| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <classpath> | |
| 3 | <classpathentry kind="src" path="src/main/java"> | |
| 4 | <attributes> | |
| 5 | <attribute name="FROM_GRADLE_MODEL" value="true"/> | |
| 6 | </attributes> | |
| 7 | </classpathentry> | |
| 8 | <classpathentry kind="src" path="src/main/resources"> | |
| 9 | <attributes> | |
| 10 | <attribute name="FROM_GRADLE_MODEL" value="true"/> | |
| 11 | </attributes> | |
| 12 | </classpathentry> | |
| 13 | <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"> | |
| 14 | <accessrules> | |
| 15 | <accessrule kind="accessible" pattern="javafx/**"/> | |
| 16 | </accessrules> | |
| 17 | </classpathentry> | |
| 18 | <classpathentry exported="true" kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/> | |
| 19 | <classpathentry kind="output" path="bin"/> | |
| 20 | </classpath> | |
| 1 | 21 |
| 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 | |
| 1 | 17 |
| 1 | /bin/ | |
| 2 | /build/ | |
| 3 | /.gradle/ | |
| 4 | /gradle/ | |
| 5 | /.nb-gradle | |
| 6 | /private | |
| 7 | .nb-gradle-properties | |
| 8 | src/main/resources/com/scrivenvar/build.sh | |
| 1 | 9 |
| 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 | GRADLE_BUILD_COMMANDS=org.eclipse.jdt.core.javabuilder | |
| 2 | GRADLE_NATURES=org.eclipse.jdt.core.javanature | |
| 3 | build.commands=org.eclipse.jdt.core.javabuilder | |
| 4 | connection.arguments= | |
| 5 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) | |
| 6 | connection.gradle.user.home=null | |
| 7 | connection.java.home=null | |
| 8 | connection.jvm.arguments= | |
| 9 | connection.project.dir= | |
| 10 | derived.resources=.gradle,build | |
| 11 | eclipse.preferences.version=1 | |
| 12 | natures=org.eclipse.jdt.core.javanature | |
| 13 | project.path=\: | |
| 1 | 14 |
| 1 | eclipse.preferences.version=1 | |
| 2 | line.separator=\n | |
| 1 | 3 |
| 1 | eclipse.preferences.version=1 | |
| 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled | |
| 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 | |
| 4 | org.eclipse.jdt.core.compiler.compliance=1.8 | |
| 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error | |
| 6 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error | |
| 7 | org.eclipse.jdt.core.compiler.source=1.8 | |
| 1 | 8 |
| 1 | eclipse.preferences.version=1 | |
| 2 | editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true | |
| 3 | org.eclipse.jdt.ui.ignorelowercasenames=true | |
| 4 | org.eclipse.jdt.ui.importorder=java;javafx;javax;org;com; | |
| 5 | org.eclipse.jdt.ui.ondemandthreshold=99 | |
| 6 | org.eclipse.jdt.ui.staticondemandthreshold=99 | |
| 7 | sp_cleanup.add_default_serial_version_id=true | |
| 8 | sp_cleanup.add_generated_serial_version_id=false | |
| 9 | sp_cleanup.add_missing_annotations=true | |
| 10 | sp_cleanup.add_missing_deprecated_annotations=true | |
| 11 | sp_cleanup.add_missing_methods=false | |
| 12 | sp_cleanup.add_missing_nls_tags=false | |
| 13 | sp_cleanup.add_missing_override_annotations=true | |
| 14 | sp_cleanup.add_missing_override_annotations_interface_methods=true | |
| 15 | sp_cleanup.add_serial_version_id=false | |
| 16 | sp_cleanup.always_use_blocks=true | |
| 17 | sp_cleanup.always_use_parentheses_in_expressions=false | |
| 18 | sp_cleanup.always_use_this_for_non_static_field_access=false | |
| 19 | sp_cleanup.always_use_this_for_non_static_method_access=false | |
| 20 | sp_cleanup.convert_functional_interfaces=false | |
| 21 | sp_cleanup.convert_to_enhanced_for_loop=false | |
| 22 | sp_cleanup.correct_indentation=false | |
| 23 | sp_cleanup.format_source_code=false | |
| 24 | sp_cleanup.format_source_code_changes_only=false | |
| 25 | sp_cleanup.insert_inferred_type_arguments=false | |
| 26 | sp_cleanup.make_local_variable_final=false | |
| 27 | sp_cleanup.make_parameters_final=false | |
| 28 | sp_cleanup.make_private_fields_final=true | |
| 29 | sp_cleanup.make_type_abstract_if_missing_method=false | |
| 30 | sp_cleanup.make_variable_declarations_final=true | |
| 31 | sp_cleanup.never_use_blocks=false | |
| 32 | sp_cleanup.never_use_parentheses_in_expressions=true | |
| 33 | sp_cleanup.on_save_use_additional_actions=true | |
| 34 | sp_cleanup.organize_imports=false | |
| 35 | sp_cleanup.qualify_static_field_accesses_with_declaring_class=false | |
| 36 | sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true | |
| 37 | sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true | |
| 38 | sp_cleanup.qualify_static_member_accesses_with_declaring_class=false | |
| 39 | sp_cleanup.qualify_static_method_accesses_with_declaring_class=false | |
| 40 | sp_cleanup.remove_private_constructors=true | |
| 41 | sp_cleanup.remove_redundant_type_arguments=true | |
| 42 | sp_cleanup.remove_trailing_whitespaces=true | |
| 43 | sp_cleanup.remove_trailing_whitespaces_all=true | |
| 44 | sp_cleanup.remove_trailing_whitespaces_ignore_empty=false | |
| 45 | sp_cleanup.remove_unnecessary_casts=true | |
| 46 | sp_cleanup.remove_unnecessary_nls_tags=false | |
| 47 | sp_cleanup.remove_unused_imports=true | |
| 48 | sp_cleanup.remove_unused_local_variables=false | |
| 49 | sp_cleanup.remove_unused_private_fields=true | |
| 50 | sp_cleanup.remove_unused_private_members=false | |
| 51 | sp_cleanup.remove_unused_private_methods=true | |
| 52 | sp_cleanup.remove_unused_private_types=true | |
| 53 | sp_cleanup.sort_members=false | |
| 54 | sp_cleanup.sort_members_all=false | |
| 55 | sp_cleanup.use_anonymous_class_creation=false | |
| 56 | sp_cleanup.use_blocks=false | |
| 57 | sp_cleanup.use_blocks_only_for_return_and_throw=false | |
| 58 | sp_cleanup.use_lambda=true | |
| 59 | sp_cleanup.use_parentheses_in_expressions=false | |
| 60 | sp_cleanup.use_this_for_non_static_field_access=false | |
| 61 | sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true | |
| 62 | sp_cleanup.use_this_for_non_static_method_access=false | |
| 63 | sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true | |
| 64 | sp_cleanup.use_type_arguments=false | |
| 1 | 65 |
| 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 | # Change Log | |
| 2 | ||
| 3 | ## 0.5 | |
| 4 | ||
| 5 | - Added document processors for Markdown and Variables | |
| 6 | - Simplified code base | |
| 7 | - Added `Ctrl+Space` hot key for quick variable injection | |
| 8 | - Replaced commonmark-java with flexmark | |
| 9 | - Added generic CARETPOSITION into document to scroll preview pane | |
| 10 | ||
| 11 | ## 0.4 | |
| 12 | ||
| 13 | - Changed name to Scrivenvar | |
| 14 | - Added hot-keys for variable mode and autocomplete | |
| 15 | - Replaced pegdown with commonmark-java | |
| 16 | - Started document processors to provide XSLT and variable dereferencing | |
| 17 | ||
| 18 | ## 0.3 | |
| 19 | ||
| 20 | - Changed name to Scrivendor | |
| 21 | - Changed logo to match | |
| 22 | - Started to implement service-oriented architecture | |
| 23 | ||
| 24 | ## 0.2 | |
| 25 | - RichTextFX (and dependencies) updated to version 0.6.10 (fixes bugs) | |
| 26 | - pegdown Markdown parser updated to version 1.6 | |
| 27 | - Added five new pegdown 1.6 extension flags to Markdown Options tab | |
| 28 | - Minor improvements | |
| 29 | ||
| 30 | ## 0.1 | |
| 31 | ||
| 32 | - Initial release | |
| 1 | 33 |
| 1 | Credits | |
| 2 | === | |
| 3 | ||
| 4 | * Dave Jarvis: [Scrivenvar](https://github.com/DaveJarvis/scrivenvar/) | |
| 5 | * Karl Tauber: [Markdown Writer FX](https://github.com/JFormDesigner/markdown-writer-fx) | |
| 6 | * 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) | |
| 7 | * Mikael Grev: [MigLayout](http://www.miglayout.com/) | |
| 8 | * Tom Eugelink: [MigPane](https://github.com/mikaelgrev/miglayout/blob/master/javafx/src/main/java/org/tbee/javafx/scene/layout/fxml/MigPane.java) | |
| 9 | * Vladimir Schneider: [flexmark](https://website.com) | |
| 10 | * Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx) | |
| 1 | 11 |
| 1 | Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot 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 |  | |
| 2 | ||
| 3 | Scrivenvar | |
| 4 | === | |
| 5 | ||
| 6 | Word processing with variables. | |
| 7 | ||
| 8 |  | |
| 9 | ||
| 10 | Features | |
| 11 | --- | |
| 12 | ||
| 13 | * User-defined variables | |
| 14 | * Preview variable values in real time | |
| 15 | * Platform independent (Windows, Linux, MacOS) | |
| 16 | * Auto-insert variable names by typing in values followed by `Control+Space` | |
| 17 | ||
| 18 | Future Features | |
| 19 | --- | |
| 20 | * Spell check | |
| 21 | * XML and XSL processing | |
| 22 | * Search and replace text with variables | |
| 23 | * R integration using [Rserve](https://rforge.net/Rserve/) | |
| 24 | ||
| 25 | Requirements | |
| 26 | --- | |
| 27 | ||
| 28 | Download and install [Java 8u40](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html). | |
| 29 | ||
| 30 | Installation | |
| 31 | --- | |
| 32 | 1. [Download](https://github.com/DaveJarvis/scrivenvar/releases) the latest zip archive and extract it to any folder. | |
| 33 | 1. Double-click `scrivenvar.jar` to start the application. | |
| 34 | ||
| 35 | License | |
| 36 | --- | |
| 37 | ||
| 38 | This software is licensed under the [BSD 2-Clause License](LICENSE). | |
| 1 | 39 |
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project> | |
| 3 | <entry key="classpath1" value="bin" /> | |
| 4 | <entry key="classpath2" value="lib/fontawesomefx-8.9.jar" /> | |
| 5 | <entry key="sourcefolders1" value="src/main/java" /> | |
| 6 | <entry key="sourcefolders2" value="src/main/resources" /> | |
| 7 | <node name="designer"> | |
| 8 | <entry key="_i18nJavaSettingsEnabled" value="true" /> | |
| 9 | <entry key="_i18nSettingsEnabled" value="true" /> | |
| 10 | <entry key="i18n.externalizeexcludes1" value="com.scrivenvar.controls.EscapeTextField#escapeCharacters" /> | |
| 11 | <entry key="i18n.externalizeexcludes2" value="com.scrivenvar.controls.WebHyperlink#uri" /> | |
| 12 | <entry key="i18n.javagetstringformat" value="Messages.get(${key})" /> | |
| 13 | </node> | |
| 14 | <node name="javacodegenerator"> | |
| 15 | <entry key="_codeStyleSettingsEnabled" value="true" /> | |
| 16 | <entry key="_generalSettingsEnabled" value="true" /> | |
| 17 | <entry key="explicitimports" value="true" /> | |
| 18 | <entry key="lineseparator" value="\n" /> | |
| 19 | </node> | |
| 20 | </project> | |
| 1 | 21 |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to write documentation (technical or otherwise) using a master copy for generating a variety of output formats, such as: HTML pages, PDFs, and EPUBs. What's more, the document provides an overview of how to use variables and--for the unintimidated--leverage the power of R, a programming language. | |
| 4 | ||
| 5 | # Software Requirements | |
| 6 | ||
| 7 | Install Java, ConTeXt, Pandoc, R, and Lib V8. Then install the R packages knitr, yaml, and devtools, and pluralize by running the following commands: | |
| 8 | ||
| 9 | sudo su - | |
| 10 | apt-get install default-jre | |
| 11 | apt-get install context | |
| 12 | apt-get install pandoc | |
| 13 | apt-get install r | |
| 14 | apt-get install libv8-dev | |
| 15 | r | |
| 16 | url <- "http://cran.us.r-project.org" | |
| 17 | install.packages('knitr', repos=url) | |
| 18 | install.packages('yaml', repos=url) | |
| 19 | install.packages('devtools', repos=url) | |
| 20 | devtools::install_github("hrbrmstr/pluralize") | |
| 21 | ||
| 22 | To exit R, press `Ctrl+d` or type `q()` followed by pressing `Enter`. | |
| 23 | ||
| 24 | The required software packages are installed. | |
| 25 | ||
| 26 | # Markdown | |
| 27 | ||
| 28 | |Table|Table| | |
| 29 | |---|---| | |
| 30 | |Data|Data| | |
| 1 | 31 |
| 1 | version = '0.5' | |
| 2 | ||
| 3 | apply plugin: 'java' | |
| 4 | apply plugin: 'java-library-distribution' | |
| 5 | apply plugin: 'application' | |
| 6 | ||
| 7 | sourceCompatibility = 1.8 | |
| 8 | ||
| 9 | mainClassName = 'com.scrivenvar.Main' | |
| 10 | ||
| 11 | repositories { | |
| 12 | jcenter() | |
| 13 | } | |
| 14 | ||
| 15 | compileJava { | |
| 16 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" | |
| 17 | } | |
| 18 | ||
| 19 | dependencies { | |
| 20 | compile group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.7-M2' | |
| 21 | compile group: 'com.miglayout', name: 'miglayout-javafx', version: '5.0' | |
| 22 | compile group: 'de.jensd', name: 'fontawesomefx-fontawesome', version: '4.5.0' | |
| 23 | compile group: 'commons-configuration', name: 'commons-configuration', version: '1.10' | |
| 24 | compile group: 'com.vladsch.flexmark', name: 'flexmark', version: '0.6.1' | |
| 25 | compile group: 'com.vladsch.flexmark', name: 'flexmark-ext-gfm-tables', version: '0.6.1' | |
| 26 | compile group: 'org.yaml', name: 'snakeyaml', version: '1.17' | |
| 27 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.4' | |
| 28 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.4' | |
| 29 | compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.8.4' | |
| 30 | compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.8.4' | |
| 31 | compile group: 'org.ahocorasick', name: 'ahocorasick', version: '0.3.0' | |
| 32 | compile group: 'junit', name: 'junit', version: '4.4' | |
| 33 | } | |
| 34 | ||
| 35 | jar { | |
| 36 | baseName = 'scrivenvar' | |
| 37 | manifest { | |
| 38 | attributes 'Main-Class': mainClassName, | |
| 39 | 'Class-Path': configurations.compile.collect { 'lib/' + it.getName() }.join(' ') | |
| 40 | } | |
| 41 | } | |
| 42 | ||
| 43 | distributions { | |
| 44 | main { | |
| 45 | baseName = 'scrivenvar' | |
| 46 | contents { | |
| 47 | from { ['LICENSE', 'README.md'] } | |
| 48 | into( 'images' ) { | |
| 49 | from { 'images' } | |
| 50 | } | |
| 51 | } | |
| 52 | } | |
| 53 | } | |
| 1 | 54 |
| 1 | 1 |
| 1 | 1 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | * @author White Magic Software, Ltd. | |
| 32 | */ | |
| 33 | public class Constants { | |
| 34 | ||
| 35 | /** | |
| 36 | * Prevent instantiation. | |
| 37 | */ | |
| 38 | private Constants() { | |
| 39 | } | |
| 40 | ||
| 41 | public static final String BUNDLE_NAME = "com.scrivenvar.messages"; | |
| 42 | public static final String SETTINGS_NAME = "/com/scrivenvar/settings.properties"; | |
| 43 | ||
| 44 | public static final String STYLESHEET_PREVIEW = "com/scrivenvar/scene.css"; | |
| 45 | public static final String STYLESHEET_EDITOR = "com/scrivenvar/editor/Markdown.css"; | |
| 46 | ||
| 47 | public static final String LOGO_32 = "com/scrivenvar/logo32.png"; | |
| 48 | public static final String LOGO_16 = "com/scrivenvar/logo16.png"; | |
| 49 | public static final String LOGO_128 = "com/scrivenvar/logo128.png"; | |
| 50 | public static final String LOGO_256 = "com/scrivenvar/logo256.png"; | |
| 51 | public static final String LOGO_512 = "com/scrivenvar/logo512.png"; | |
| 52 | ||
| 53 | /** | |
| 54 | * Separates YAML variable nodes (e.g., the dots in <code>$root.node.var$</code>). | |
| 55 | */ | |
| 56 | public static final String SEPARATOR = "."; | |
| 57 | ||
| 58 | public static final String CARET_POSITION = "CARETPOSITION"; | |
| 59 | public static final String MD_CARET_POSITION = "${" + CARET_POSITION + "}"; | |
| 60 | public static final String XML_CARET_POSITION = "<![CDATA[" + MD_CARET_POSITION + "]]>"; | |
| 61 | } | |
| 1 | 62 |
| 1 | /* | |
| 2 | * Copyright 2016 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.editor.EditorPane; | |
| 29 | import com.scrivenvar.editor.MarkdownEditorPane; | |
| 30 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 31 | import com.scrivenvar.service.Options; | |
| 32 | import com.scrivenvar.service.events.AlertMessage; | |
| 33 | import com.scrivenvar.service.events.AlertService; | |
| 34 | import java.io.IOException; | |
| 35 | import java.nio.file.Files; | |
| 36 | import java.nio.file.Path; | |
| 37 | import java.util.function.Consumer; | |
| 38 | import javafx.application.Platform; | |
| 39 | import javafx.beans.binding.Bindings; | |
| 40 | import javafx.beans.property.BooleanProperty; | |
| 41 | import javafx.beans.property.ReadOnlyBooleanProperty; | |
| 42 | import javafx.beans.property.ReadOnlyBooleanWrapper; | |
| 43 | import javafx.beans.property.SimpleBooleanProperty; | |
| 44 | import javafx.event.Event; | |
| 45 | import javafx.scene.control.Alert; | |
| 46 | import javafx.scene.control.SplitPane; | |
| 47 | import javafx.scene.control.Tab; | |
| 48 | import javafx.scene.control.Tooltip; | |
| 49 | import javafx.scene.input.InputEvent; | |
| 50 | import javafx.scene.text.Text; | |
| 51 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 52 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 53 | import org.fxmisc.undo.UndoManager; | |
| 54 | import org.fxmisc.wellbehaved.event.EventPattern; | |
| 55 | import org.fxmisc.wellbehaved.event.InputMap; | |
| 56 | ||
| 57 | /** | |
| 58 | * Editor for a single file. | |
| 59 | * | |
| 60 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 61 | */ | |
| 62 | public final class FileEditorTab extends Tab { | |
| 63 | ||
| 64 | private final Options options = Services.load( Options.class ); | |
| 65 | private final AlertService alertService = Services.load( AlertService.class ); | |
| 66 | ||
| 67 | private EditorPane editorPane; | |
| 68 | private HTMLPreviewPane previewPane; | |
| 69 | ||
| 70 | private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper(); | |
| 71 | private final BooleanProperty canUndo = new SimpleBooleanProperty(); | |
| 72 | private final BooleanProperty canRedo = new SimpleBooleanProperty(); | |
| 73 | private Path path; | |
| 74 | ||
| 75 | FileEditorTab( final Path path ) { | |
| 76 | setPath( path ); | |
| 77 | setUserData( this ); | |
| 78 | ||
| 79 | this.modified.addListener( (observable, oldPath, newPath) -> updateTab() ); | |
| 80 | updateTab(); | |
| 81 | ||
| 82 | setOnSelectionChanged( e -> { | |
| 83 | if( isSelected() ) { | |
| 84 | Platform.runLater( () -> activated() ); | |
| 85 | } | |
| 86 | } ); | |
| 87 | } | |
| 88 | ||
| 89 | private void updateTab() { | |
| 90 | final Path filePath = getPath(); | |
| 91 | ||
| 92 | setText( getFilename( filePath ) ); | |
| 93 | setGraphic( getModifiedMark() ); | |
| 94 | setTooltip( getTooltip( filePath ) ); | |
| 95 | } | |
| 96 | ||
| 97 | private String getFilename( final Path filePath ) { | |
| 98 | return (filePath == null) | |
| 99 | ? Messages.get( "FileEditor.untitled" ) | |
| 100 | : filePath.getFileName().toString(); | |
| 101 | } | |
| 102 | ||
| 103 | private Tooltip getTooltip( final Path filePath ) { | |
| 104 | return (filePath == null) | |
| 105 | ? null | |
| 106 | : new Tooltip( filePath.toString() ); | |
| 107 | } | |
| 108 | ||
| 109 | private Text getModifiedMark() { | |
| 110 | return isModified() ? new Text( "*" ) : null; | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Called when the user switches tab. | |
| 115 | */ | |
| 116 | private void activated() { | |
| 117 | if( getTabPane() == null || !isSelected() ) { | |
| 118 | // Tab is closed or no longer active. | |
| 119 | return; | |
| 120 | } | |
| 121 | ||
| 122 | if( getContent() != null ) { | |
| 123 | getEditorPane().requestFocus(); | |
| 124 | return; | |
| 125 | } | |
| 126 | ||
| 127 | // Load the text and update the preview before the undo manager. | |
| 128 | load(); | |
| 129 | ||
| 130 | // Track undo requests (must not be called before load). | |
| 131 | initUndoManager(); | |
| 132 | initSplitPane(); | |
| 133 | } | |
| 134 | ||
| 135 | public void initSplitPane() { | |
| 136 | final EditorPane editor = getEditorPane(); | |
| 137 | final HTMLPreviewPane preview = getPreviewPane(); | |
| 138 | ||
| 139 | final VirtualizedScrollPane<StyleClassedTextArea> editorScrollPane = editor.getScrollPane(); | |
| 140 | ||
| 141 | // Make the preview pane scroll correspond to the editor pane scroll. | |
| 142 | // Separate the edit and preview panels. | |
| 143 | final SplitPane splitPane = new SplitPane( | |
| 144 | editorScrollPane, | |
| 145 | preview.getWebView() ); | |
| 146 | setContent( splitPane ); | |
| 147 | ||
| 148 | // Let the user edit. | |
| 149 | editor.requestFocus(); | |
| 150 | } | |
| 151 | ||
| 152 | private void initUndoManager() { | |
| 153 | final UndoManager undoManager = getUndoManager(); | |
| 154 | ||
| 155 | // Clear undo history after first load. | |
| 156 | undoManager.forgetHistory(); | |
| 157 | ||
| 158 | // Bind the editor undo manager to the properties. | |
| 159 | modified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) ); | |
| 160 | canUndo.bind( undoManager.undoAvailableProperty() ); | |
| 161 | canRedo.bind( undoManager.redoAvailableProperty() ); | |
| 162 | } | |
| 163 | ||
| 164 | /** | |
| 165 | * Returns the index into the text where the caret blinks happily away. | |
| 166 | * | |
| 167 | * @return A number from 0 to the editor's document text length. | |
| 168 | */ | |
| 169 | public int getCaretPosition() { | |
| 170 | return getEditorPane().getEditor().getCaretPosition(); | |
| 171 | } | |
| 172 | ||
| 173 | void load() { | |
| 174 | final Path filePath = getPath(); | |
| 175 | ||
| 176 | if( filePath != null ) { | |
| 177 | try { | |
| 178 | final byte[] bytes = Files.readAllBytes( filePath ); | |
| 179 | String markdown; | |
| 180 | ||
| 181 | try { | |
| 182 | markdown = new String( bytes, getOptions().getEncoding() ); | |
| 183 | } catch( Exception e ) { | |
| 184 | // Unsupported encodings and null pointers fallback here. | |
| 185 | markdown = new String( bytes ); | |
| 186 | } | |
| 187 | ||
| 188 | getEditorPane().setText( markdown ); | |
| 189 | } catch( IOException ex ) { | |
| 190 | final AlertMessage message = getAlertService().createAlertMessage( | |
| 191 | Messages.get( "FileEditor.loadFailed.title" ), | |
| 192 | Messages.get( "FileEditor.loadFailed.message" ), | |
| 193 | filePath, | |
| 194 | ex.getMessage() | |
| 195 | ); | |
| 196 | ||
| 197 | final Alert alert = getAlertService().createAlertError( message ); | |
| 198 | ||
| 199 | alert.showAndWait(); | |
| 200 | } | |
| 201 | } | |
| 202 | } | |
| 203 | ||
| 204 | boolean save() { | |
| 205 | final String text = getEditorPane().getText(); | |
| 206 | ||
| 207 | byte[] bytes; | |
| 208 | ||
| 209 | try { | |
| 210 | bytes = text.getBytes( getOptions().getEncoding() ); | |
| 211 | } catch( Exception ex ) { | |
| 212 | bytes = text.getBytes(); | |
| 213 | } | |
| 214 | ||
| 215 | try { | |
| 216 | Files.write( getPath(), bytes ); | |
| 217 | getEditorPane().getUndoManager().mark(); | |
| 218 | return true; | |
| 219 | } catch( IOException ex ) { | |
| 220 | final AlertService service = getAlertService(); | |
| 221 | final AlertMessage message = service.createAlertMessage( | |
| 222 | Messages.get( "FileEditor.saveFailed.title" ), | |
| 223 | Messages.get( "FileEditor.saveFailed.message" ), | |
| 224 | getPath(), | |
| 225 | ex.getMessage() | |
| 226 | ); | |
| 227 | ||
| 228 | final Alert alert = service.createAlertError( message ); | |
| 229 | ||
| 230 | alert.showAndWait(); | |
| 231 | return false; | |
| 232 | } | |
| 233 | } | |
| 234 | ||
| 235 | Path getPath() { | |
| 236 | return this.path; | |
| 237 | } | |
| 238 | ||
| 239 | void setPath( final Path path ) { | |
| 240 | this.path = path; | |
| 241 | } | |
| 242 | ||
| 243 | boolean isModified() { | |
| 244 | return this.modified.get(); | |
| 245 | } | |
| 246 | ||
| 247 | ReadOnlyBooleanProperty modifiedProperty() { | |
| 248 | return this.modified.getReadOnlyProperty(); | |
| 249 | } | |
| 250 | ||
| 251 | BooleanProperty canUndoProperty() { | |
| 252 | return this.canUndo; | |
| 253 | } | |
| 254 | ||
| 255 | BooleanProperty canRedoProperty() { | |
| 256 | return this.canRedo; | |
| 257 | } | |
| 258 | ||
| 259 | private UndoManager getUndoManager() { | |
| 260 | return getEditorPane().getUndoManager(); | |
| 261 | } | |
| 262 | ||
| 263 | public <T extends Event, U extends T> void addEventListener( | |
| 264 | final EventPattern<? super T, ? extends U> event, | |
| 265 | final Consumer<? super U> consumer ) { | |
| 266 | getEditorPane().addEventListener( event, consumer ); | |
| 267 | } | |
| 268 | ||
| 269 | public void addEventListener( final InputMap<InputEvent> map ) { | |
| 270 | getEditorPane().addEventListener( map ); | |
| 271 | } | |
| 272 | ||
| 273 | public void removeEventListener( final InputMap<InputEvent> map ) { | |
| 274 | getEditorPane().removeEventListener( map ); | |
| 275 | } | |
| 276 | ||
| 277 | protected EditorPane getEditorPane() { | |
| 278 | if( this.editorPane == null ) { | |
| 279 | this.editorPane = new MarkdownEditorPane(); | |
| 280 | } | |
| 281 | ||
| 282 | return this.editorPane; | |
| 283 | } | |
| 284 | ||
| 285 | private AlertService getAlertService() { | |
| 286 | return this.alertService; | |
| 287 | } | |
| 288 | ||
| 289 | private Options getOptions() { | |
| 290 | return this.options; | |
| 291 | } | |
| 292 | ||
| 293 | public HTMLPreviewPane getPreviewPane() { | |
| 294 | if( this.previewPane == null ) { | |
| 295 | this.previewPane = new HTMLPreviewPane( getPath() ); | |
| 296 | } | |
| 297 | ||
| 298 | return this.previewPane; | |
| 299 | } | |
| 300 | } | |
| 1 | 301 |
| 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; | |
| 29 | ||
| 30 | import static com.scrivenvar.Messages.get; | |
| 31 | import com.scrivenvar.predicates.files.FileTypePredicate; | |
| 32 | import com.scrivenvar.service.Options; | |
| 33 | import com.scrivenvar.service.Settings; | |
| 34 | import com.scrivenvar.service.events.AlertMessage; | |
| 35 | import com.scrivenvar.service.events.AlertService; | |
| 36 | import static com.scrivenvar.service.events.AlertService.NO; | |
| 37 | import static com.scrivenvar.service.events.AlertService.YES; | |
| 38 | import com.scrivenvar.util.Utils; | |
| 39 | import java.io.File; | |
| 40 | import java.nio.file.Path; | |
| 41 | import java.util.ArrayList; | |
| 42 | import java.util.List; | |
| 43 | import java.util.function.Consumer; | |
| 44 | import java.util.prefs.Preferences; | |
| 45 | import java.util.stream.Collectors; | |
| 46 | import javafx.beans.property.ReadOnlyBooleanProperty; | |
| 47 | import javafx.beans.property.ReadOnlyBooleanWrapper; | |
| 48 | import javafx.beans.property.ReadOnlyObjectProperty; | |
| 49 | import javafx.beans.property.ReadOnlyObjectWrapper; | |
| 50 | import javafx.beans.value.ChangeListener; | |
| 51 | import javafx.beans.value.ObservableValue; | |
| 52 | import javafx.collections.ListChangeListener; | |
| 53 | import javafx.collections.ObservableList; | |
| 54 | import javafx.event.Event; | |
| 55 | import javafx.scene.Node; | |
| 56 | import javafx.scene.control.Alert; | |
| 57 | import javafx.scene.control.ButtonType; | |
| 58 | import javafx.scene.control.Tab; | |
| 59 | import javafx.scene.control.TabPane; | |
| 60 | import javafx.scene.control.TabPane.TabClosingPolicy; | |
| 61 | import javafx.scene.input.InputEvent; | |
| 62 | import javafx.stage.FileChooser; | |
| 63 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 64 | import javafx.stage.Window; | |
| 65 | import org.fxmisc.richtext.StyledTextArea; | |
| 66 | import org.fxmisc.wellbehaved.event.EventPattern; | |
| 67 | import org.fxmisc.wellbehaved.event.InputMap; | |
| 68 | ||
| 69 | /** | |
| 70 | * Tab pane for file editors. | |
| 71 | * | |
| 72 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 73 | */ | |
| 74 | public class FileEditorTabPane extends TabPane implements ChangeListener<Tab> { | |
| 75 | ||
| 76 | private final static String FILTER_PREFIX = "Dialog.file.choose.filter"; | |
| 77 | ||
| 78 | private final Options options = Services.load( Options.class ); | |
| 79 | private final Settings settings = Services.load( Settings.class ); | |
| 80 | private final AlertService alertService = Services.load( AlertService.class ); | |
| 81 | ||
| 82 | private final ReadOnlyObjectWrapper<FileEditorTab> activeFileEditor = new ReadOnlyObjectWrapper<>(); | |
| 83 | private final ReadOnlyBooleanWrapper anyFileEditorModified = new ReadOnlyBooleanWrapper(); | |
| 84 | ||
| 85 | public FileEditorTabPane() { | |
| 86 | final ObservableList<Tab> tabs = getTabs(); | |
| 87 | ||
| 88 | setFocusTraversable( false ); | |
| 89 | setTabClosingPolicy( TabClosingPolicy.ALL_TABS ); | |
| 90 | ||
| 91 | // Observe the tab so that when a new tab is opened or selected, | |
| 92 | // a notification is kicked off. | |
| 93 | getSelectionModel().selectedItemProperty().addListener( this ); | |
| 94 | ||
| 95 | // update anyFileEditorModified property | |
| 96 | final ChangeListener<Boolean> modifiedListener = (observable, oldValue, newValue) -> { | |
| 97 | for( final Tab tab : tabs ) { | |
| 98 | if( ((FileEditorTab)tab.getUserData()).isModified() ) { | |
| 99 | this.anyFileEditorModified.set( true ); | |
| 100 | break; | |
| 101 | } | |
| 102 | } | |
| 103 | }; | |
| 104 | ||
| 105 | tabs.addListener( (ListChangeListener<Tab>)change -> { | |
| 106 | while( change.next() ) { | |
| 107 | if( change.wasAdded() ) { | |
| 108 | change.getAddedSubList().stream().forEach( (tab) -> { | |
| 109 | ((FileEditorTab)tab.getUserData()).modifiedProperty().addListener( modifiedListener ); | |
| 110 | } ); | |
| 111 | } else if( change.wasRemoved() ) { | |
| 112 | change.getRemoved().stream().forEach( (tab) -> { | |
| 113 | ((FileEditorTab)tab.getUserData()).modifiedProperty().removeListener( modifiedListener ); | |
| 114 | } ); | |
| 115 | } | |
| 116 | } | |
| 117 | ||
| 118 | // Changes in the tabs may also change anyFileEditorModified property | |
| 119 | // (e.g. closed modified file) | |
| 120 | modifiedListener.changed( null, null, null ); | |
| 121 | } ); | |
| 122 | } | |
| 123 | ||
| 124 | public <T extends Event, U extends T> void addEventListener( | |
| 125 | final EventPattern<? super T, ? extends U> event, | |
| 126 | final Consumer<? super U> consumer ) { | |
| 127 | getActiveFileEditor().addEventListener( event, consumer ); | |
| 128 | } | |
| 129 | ||
| 130 | /** | |
| 131 | * Delegates to the active file editor pane, and, ultimately, to its text | |
| 132 | * area. | |
| 133 | * | |
| 134 | * @param map The map of methods to events. | |
| 135 | */ | |
| 136 | public void addEventListener( final InputMap<InputEvent> map ) { | |
| 137 | getActiveFileEditor().addEventListener( map ); | |
| 138 | } | |
| 139 | ||
| 140 | public void removeEventListener( final InputMap<InputEvent> map ) { | |
| 141 | getActiveFileEditor().removeEventListener( map ); | |
| 142 | } | |
| 143 | ||
| 144 | @Override | |
| 145 | public void changed( | |
| 146 | final ObservableValue<? extends Tab> observable, | |
| 147 | final Tab oldTab, | |
| 148 | final Tab newTab ) { | |
| 149 | ||
| 150 | if( newTab != null ) { | |
| 151 | this.activeFileEditor.set( (FileEditorTab)newTab.getUserData() ); | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | Node getNode() { | |
| 156 | return this; | |
| 157 | } | |
| 158 | ||
| 159 | /** | |
| 160 | * Allows clients to manipulate the editor content directly. | |
| 161 | * | |
| 162 | * @return The text area for the active file editor. | |
| 163 | */ | |
| 164 | public StyledTextArea getEditor() { | |
| 165 | return getActiveFileEditor().getEditorPane().getEditor(); | |
| 166 | } | |
| 167 | ||
| 168 | public FileEditorTab getActiveFileEditor() { | |
| 169 | return this.activeFileEditor.get(); | |
| 170 | } | |
| 171 | ||
| 172 | ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() { | |
| 173 | return this.activeFileEditor.getReadOnlyProperty(); | |
| 174 | } | |
| 175 | ||
| 176 | ReadOnlyBooleanProperty anyFileEditorModifiedProperty() { | |
| 177 | return this.anyFileEditorModified.getReadOnlyProperty(); | |
| 178 | } | |
| 179 | ||
| 180 | private FileEditorTab createFileEditor( final Path path ) { | |
| 181 | final FileEditorTab tab = new FileEditorTab( path ); | |
| 182 | ||
| 183 | tab.setOnCloseRequest( e -> { | |
| 184 | if( !canCloseEditor( tab ) ) { | |
| 185 | e.consume(); | |
| 186 | } | |
| 187 | } ); | |
| 188 | ||
| 189 | return tab; | |
| 190 | } | |
| 191 | ||
| 192 | /** | |
| 193 | * Called when the user selects New from the File menu. | |
| 194 | * | |
| 195 | * @return The newly added tab. | |
| 196 | */ | |
| 197 | FileEditorTab newEditor() { | |
| 198 | final FileEditorTab tab = createFileEditor( null ); | |
| 199 | ||
| 200 | getTabs().add( tab ); | |
| 201 | getSelectionModel().select( tab ); | |
| 202 | return tab; | |
| 203 | } | |
| 204 | ||
| 205 | List<FileEditorTab> openFileDialog() { | |
| 206 | final FileChooser dialog | |
| 207 | = createFileChooser( get( "Dialog.file.choose.open.title" ) ); | |
| 208 | final List<File> files = dialog.showOpenMultipleDialog( getWindow() ); | |
| 209 | ||
| 210 | return (files != null && !files.isEmpty()) | |
| 211 | ? openFiles( files ) | |
| 212 | : new ArrayList<>(); | |
| 213 | } | |
| 214 | ||
| 215 | /** | |
| 216 | * Opens the files into new editors, unless one of those files was a | |
| 217 | * definition file. The definition file is loaded into the definition pane, | |
| 218 | * but only the first one selected (multiple definition files will result in a | |
| 219 | * warning). | |
| 220 | * | |
| 221 | * @param files The list of non-definition files that the were requested to | |
| 222 | * open. | |
| 223 | * | |
| 224 | * @return A list of files that can be opened in text editors. | |
| 225 | */ | |
| 226 | private List<FileEditorTab> openFiles( final List<File> files ) { | |
| 227 | final List<FileEditorTab> openedEditors = new ArrayList<>(); | |
| 228 | ||
| 229 | final FileTypePredicate predicate | |
| 230 | = new FileTypePredicate( createExtensionFilter( "definition" ).getExtensions() ); | |
| 231 | ||
| 232 | // The user might have opened muliple definitions files. These will | |
| 233 | // be discarded from the text editable files. | |
| 234 | final List<File> definitions | |
| 235 | = files.stream().filter( predicate ).collect( Collectors.toList() ); | |
| 236 | ||
| 237 | // Create a modifiable list to remove any definition files that were | |
| 238 | // opened. | |
| 239 | final List<File> editors = new ArrayList<>( files ); | |
| 240 | editors.removeAll( definitions ); | |
| 241 | ||
| 242 | // If there are any editor-friendly files opened (e.g,. Markdown, XML), then | |
| 243 | // open them up in new tabs. | |
| 244 | if( editors.size() > 0 ) { | |
| 245 | saveLastDirectory( editors.get( 0 ) ); | |
| 246 | openedEditors.addAll( openEditors( editors, 0 ) ); | |
| 247 | } | |
| 248 | ||
| 249 | if( definitions.size() > 0 ) { | |
| 250 | openDefinition( definitions.get( 0 ) ); | |
| 251 | } | |
| 252 | ||
| 253 | return openedEditors; | |
| 254 | } | |
| 255 | ||
| 256 | private List<FileEditorTab> openEditors( final List<File> files, final int activeIndex ) { | |
| 257 | final List<FileEditorTab> editors = new ArrayList<>(); | |
| 258 | final List<Tab> tabs = getTabs(); | |
| 259 | ||
| 260 | // Close single unmodified "Untitled" tab. | |
| 261 | if( tabs.size() == 1 ) { | |
| 262 | final FileEditorTab fileEditor = (FileEditorTab)(tabs.get( 0 ).getUserData()); | |
| 263 | ||
| 264 | if( fileEditor.getPath() == null && !fileEditor.isModified() ) { | |
| 265 | closeEditor( fileEditor, false ); | |
| 266 | } | |
| 267 | } | |
| 268 | ||
| 269 | for( int i = 0; i < files.size(); i++ ) { | |
| 270 | Path path = files.get( i ).toPath(); | |
| 271 | ||
| 272 | // Check whether file is already opened. | |
| 273 | FileEditorTab fileEditor = findEditor( path ); | |
| 274 | ||
| 275 | if( fileEditor == null ) { | |
| 276 | fileEditor = createFileEditor( path ); | |
| 277 | getTabs().add( fileEditor ); | |
| 278 | editors.add( fileEditor ); | |
| 279 | } | |
| 280 | ||
| 281 | // Select first file. | |
| 282 | if( i == activeIndex ) { | |
| 283 | getSelectionModel().select( fileEditor ); | |
| 284 | } | |
| 285 | } | |
| 286 | ||
| 287 | return editors; | |
| 288 | } | |
| 289 | ||
| 290 | /** | |
| 291 | * Called when the user has opened a definition file (using the file open | |
| 292 | * dialog box). This will replace the current set of definitions for the | |
| 293 | * active tab. | |
| 294 | * | |
| 295 | * @param definition The file to open. | |
| 296 | */ | |
| 297 | private void openDefinition( final File definition ) { | |
| 298 | System.out.println( "open definition file: " + definition.toString() ); | |
| 299 | } | |
| 300 | ||
| 301 | boolean saveEditor( final FileEditorTab fileEditor ) { | |
| 302 | if( fileEditor == null || !fileEditor.isModified() ) { | |
| 303 | return true; | |
| 304 | } | |
| 305 | ||
| 306 | if( fileEditor.getPath() == null ) { | |
| 307 | getSelectionModel().select( fileEditor ); | |
| 308 | ||
| 309 | final FileChooser fileChooser = createFileChooser( Messages.get( "Dialog.file.choose.save.title" ) ); | |
| 310 | final File file = fileChooser.showSaveDialog( getWindow() ); | |
| 311 | if( file == null ) { | |
| 312 | return false; | |
| 313 | } | |
| 314 | ||
| 315 | saveLastDirectory( file ); | |
| 316 | fileEditor.setPath( file.toPath() ); | |
| 317 | } | |
| 318 | ||
| 319 | return fileEditor.save(); | |
| 320 | } | |
| 321 | ||
| 322 | boolean saveAllEditors() { | |
| 323 | final FileEditorTab[] allEditors = getAllEditors(); | |
| 324 | ||
| 325 | boolean success = true; | |
| 326 | for( FileEditorTab fileEditor : allEditors ) { | |
| 327 | if( !saveEditor( fileEditor ) ) { | |
| 328 | success = false; | |
| 329 | } | |
| 330 | } | |
| 331 | ||
| 332 | return success; | |
| 333 | } | |
| 334 | ||
| 335 | boolean canCloseEditor( final FileEditorTab tab ) { | |
| 336 | if( !tab.isModified() ) { | |
| 337 | return true; | |
| 338 | } | |
| 339 | ||
| 340 | final AlertMessage message = getAlertService().createAlertMessage( | |
| 341 | Messages.get( "Alert.file.close.title" ), | |
| 342 | Messages.get( "Alert.file.close.text" ), | |
| 343 | tab.getText() | |
| 344 | ); | |
| 345 | ||
| 346 | final Alert alert = getAlertService().createAlertConfirmation( message ); | |
| 347 | final ButtonType response = alert.showAndWait().get(); | |
| 348 | ||
| 349 | return response == YES ? saveEditor( tab ) : response == NO; | |
| 350 | } | |
| 351 | ||
| 352 | private AlertService getAlertService() { | |
| 353 | return this.alertService; | |
| 354 | } | |
| 355 | ||
| 356 | boolean closeEditor( FileEditorTab fileEditor, boolean save ) { | |
| 357 | if( fileEditor == null ) { | |
| 358 | return true; | |
| 359 | } | |
| 360 | ||
| 361 | final Tab tab = fileEditor; | |
| 362 | ||
| 363 | if( save ) { | |
| 364 | Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT ); | |
| 365 | Event.fireEvent( tab, event ); | |
| 366 | if( event.isConsumed() ) { | |
| 367 | return false; | |
| 368 | } | |
| 369 | } | |
| 370 | ||
| 371 | getTabs().remove( tab ); | |
| 372 | if( tab.getOnClosed() != null ) { | |
| 373 | Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) ); | |
| 374 | } | |
| 375 | ||
| 376 | return true; | |
| 377 | } | |
| 378 | ||
| 379 | boolean closeAllEditors() { | |
| 380 | FileEditorTab[] allEditors = getAllEditors(); | |
| 381 | FileEditorTab activeEditor = activeFileEditor.get(); | |
| 382 | ||
| 383 | // try to save active tab first because in case the user decides to cancel, | |
| 384 | // then it stays active | |
| 385 | if( activeEditor != null && !canCloseEditor( activeEditor ) ) { | |
| 386 | return false; | |
| 387 | } | |
| 388 | ||
| 389 | // save modified tabs | |
| 390 | for( int i = 0; i < allEditors.length; i++ ) { | |
| 391 | FileEditorTab fileEditor = allEditors[ i ]; | |
| 392 | if( fileEditor == activeEditor ) { | |
| 393 | continue; | |
| 394 | } | |
| 395 | ||
| 396 | if( fileEditor.isModified() ) { | |
| 397 | // activate the modified tab to make its modified content visible to the user | |
| 398 | getSelectionModel().select( i ); | |
| 399 | ||
| 400 | if( !canCloseEditor( fileEditor ) ) { | |
| 401 | return false; | |
| 402 | } | |
| 403 | } | |
| 404 | } | |
| 405 | ||
| 406 | // Close all tabs. | |
| 407 | for( final FileEditorTab fileEditor : allEditors ) { | |
| 408 | if( !closeEditor( fileEditor, false ) ) { | |
| 409 | return false; | |
| 410 | } | |
| 411 | } | |
| 412 | ||
| 413 | saveState( allEditors, activeEditor ); | |
| 414 | ||
| 415 | return getTabs().isEmpty(); | |
| 416 | } | |
| 417 | ||
| 418 | private FileEditorTab[] getAllEditors() { | |
| 419 | final ObservableList<Tab> tabs = getTabs(); | |
| 420 | final FileEditorTab[] allEditors = new FileEditorTab[ tabs.size() ]; | |
| 421 | final int length = tabs.size(); | |
| 422 | ||
| 423 | for( int i = 0; i < length; i++ ) { | |
| 424 | allEditors[ i ] = (FileEditorTab)tabs.get( i ).getUserData(); | |
| 425 | } | |
| 426 | ||
| 427 | return allEditors; | |
| 428 | } | |
| 429 | ||
| 430 | private FileEditorTab findEditor( Path path ) { | |
| 431 | for( final Tab tab : getTabs() ) { | |
| 432 | final FileEditorTab fileEditor = (FileEditorTab)tab.getUserData(); | |
| 433 | ||
| 434 | if( path.equals( fileEditor.getPath() ) ) { | |
| 435 | return fileEditor; | |
| 436 | } | |
| 437 | } | |
| 438 | ||
| 439 | return null; | |
| 440 | } | |
| 441 | ||
| 442 | private Settings getSettings() { | |
| 443 | return this.settings; | |
| 444 | } | |
| 445 | ||
| 446 | private FileChooser createFileChooser( String title ) { | |
| 447 | final FileChooser fileChooser = new FileChooser(); | |
| 448 | ||
| 449 | fileChooser.setTitle( title ); | |
| 450 | fileChooser.getExtensionFilters().addAll( | |
| 451 | createExtensionFilters() ); | |
| 452 | ||
| 453 | final String lastDirectory = getState().get( "lastDirectory", null ); | |
| 454 | File file = new File( (lastDirectory != null) ? lastDirectory : "." ); | |
| 455 | ||
| 456 | if( !file.isDirectory() ) { | |
| 457 | file = new File( "." ); | |
| 458 | } | |
| 459 | ||
| 460 | fileChooser.setInitialDirectory( file ); | |
| 461 | return fileChooser; | |
| 462 | } | |
| 463 | ||
| 464 | private List<ExtensionFilter> createExtensionFilters() { | |
| 465 | final List<ExtensionFilter> list = new ArrayList<>(); | |
| 466 | ||
| 467 | // TODO: Return a list of all properties that match the filter prefix. | |
| 468 | // This will allow dynamic filters to be added and removed just by | |
| 469 | // updating the properties file. | |
| 470 | list.add( createExtensionFilter( "markdown" ) ); | |
| 471 | list.add( createExtensionFilter( "definition" ) ); | |
| 472 | list.add( createExtensionFilter( "xml" ) ); | |
| 473 | list.add( createExtensionFilter( "all" ) ); | |
| 474 | return list; | |
| 475 | } | |
| 476 | ||
| 477 | private ExtensionFilter createExtensionFilter( final String filetype ) { | |
| 478 | final String tKey = String.format( "%s.title.%s", FILTER_PREFIX, filetype ); | |
| 479 | final String eKey = String.format( "%s.ext.%s", FILTER_PREFIX, filetype ); | |
| 480 | ||
| 481 | return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) ); | |
| 482 | } | |
| 483 | ||
| 484 | private List<String> getExtensions( final String key ) { | |
| 485 | return getStringSettingList( key ); | |
| 486 | } | |
| 487 | ||
| 488 | private List<String> getStringSettingList( String key ) { | |
| 489 | return getStringSettingList( key, null ); | |
| 490 | } | |
| 491 | ||
| 492 | private List<String> getStringSettingList( String key, List<String> values ) { | |
| 493 | return getSettings().getStringSettingList( key, values ); | |
| 494 | } | |
| 495 | ||
| 496 | private void saveLastDirectory( File file ) { | |
| 497 | getState().put( "lastDirectory", file.getParent() ); | |
| 498 | } | |
| 499 | ||
| 500 | public void restoreState() { | |
| 501 | int activeIndex = 0; | |
| 502 | ||
| 503 | final Preferences state = getState(); | |
| 504 | final String[] fileNames = Utils.getPrefsStrings( state, "file" ); | |
| 505 | final String activeFileName = state.get( "activeFile", null ); | |
| 506 | ||
| 507 | final ArrayList<File> files = new ArrayList<>( fileNames.length ); | |
| 508 | ||
| 509 | for( final String fileName : fileNames ) { | |
| 510 | final File file = new File( fileName ); | |
| 511 | ||
| 512 | if( file.exists() ) { | |
| 513 | files.add( file ); | |
| 514 | ||
| 515 | if( fileName.equals( activeFileName ) ) { | |
| 516 | activeIndex = files.size() - 1; | |
| 517 | } | |
| 518 | } | |
| 519 | } | |
| 520 | ||
| 521 | if( files.isEmpty() ) { | |
| 522 | newEditor(); | |
| 523 | return; | |
| 524 | } | |
| 525 | ||
| 526 | openEditors( files, activeIndex ); | |
| 527 | } | |
| 528 | ||
| 529 | private void saveState( final FileEditorTab[] allEditors, final FileEditorTab activeEditor ) { | |
| 530 | final List<String> fileNames = new ArrayList<>( allEditors.length ); | |
| 531 | ||
| 532 | for( final FileEditorTab fileEditor : allEditors ) { | |
| 533 | if( fileEditor.getPath() != null ) { | |
| 534 | fileNames.add( fileEditor.getPath().toString() ); | |
| 535 | } | |
| 536 | } | |
| 537 | ||
| 538 | final Preferences state = getState(); | |
| 539 | Utils.putPrefsStrings( state, "file", fileNames.toArray( new String[ fileNames.size() ] ) ); | |
| 540 | ||
| 541 | if( activeEditor != null && activeEditor.getPath() != null ) { | |
| 542 | state.put( "activeFile", activeEditor.getPath().toString() ); | |
| 543 | } else { | |
| 544 | state.remove( "activeFile" ); | |
| 545 | } | |
| 546 | } | |
| 547 | ||
| 548 | private Window getWindow() { | |
| 549 | return getScene().getWindow(); | |
| 550 | } | |
| 551 | ||
| 552 | protected Options getOptions() { | |
| 553 | return this.options; | |
| 554 | } | |
| 555 | ||
| 556 | protected Preferences getState() { | |
| 557 | return getOptions().getState(); | |
| 558 | } | |
| 559 | ||
| 560 | } | |
| 1 | 561 |
| 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; | |
| 29 | ||
| 30 | import static com.scrivenvar.Constants.LOGO_128; | |
| 31 | import static com.scrivenvar.Constants.LOGO_16; | |
| 32 | import static com.scrivenvar.Constants.LOGO_256; | |
| 33 | import static com.scrivenvar.Constants.LOGO_32; | |
| 34 | import static com.scrivenvar.Constants.LOGO_512; | |
| 35 | import com.scrivenvar.service.Options; | |
| 36 | import com.scrivenvar.service.events.AlertService; | |
| 37 | import com.scrivenvar.util.StageState; | |
| 38 | import javafx.application.Application; | |
| 39 | import javafx.scene.Scene; | |
| 40 | import javafx.scene.image.Image; | |
| 41 | import javafx.stage.Stage; | |
| 42 | ||
| 43 | /** | |
| 44 | * Main application entry point. The application allows users to edit Markdown | |
| 45 | * files and see a real-time preview of the edits. | |
| 46 | * | |
| 47 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 48 | */ | |
| 49 | public final class Main extends Application { | |
| 50 | ||
| 51 | private static Application app; | |
| 52 | ||
| 53 | private final MainWindow mainWindow = new MainWindow(); | |
| 54 | private final Options options = Services.load( Options.class ); | |
| 55 | ||
| 56 | public static void main( String[] args ) { | |
| 57 | launch( args ); | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Application entry point. | |
| 62 | * | |
| 63 | * @param stage The primary application stage. | |
| 64 | * | |
| 65 | * @throws Exception Could not read configuration file. | |
| 66 | */ | |
| 67 | @Override | |
| 68 | public void start( final Stage stage ) throws Exception { | |
| 69 | initApplication(); | |
| 70 | initState( stage ); | |
| 71 | initStage( stage ); | |
| 72 | initAlertService(); | |
| 73 | ||
| 74 | stage.show(); | |
| 75 | } | |
| 76 | ||
| 77 | private void initApplication() { | |
| 78 | app = this; | |
| 79 | } | |
| 80 | ||
| 81 | private Options getOptions() { | |
| 82 | return this.options; | |
| 83 | } | |
| 84 | ||
| 85 | private String getApplicationTitle() { | |
| 86 | return Messages.get( "Main.title" ); | |
| 87 | } | |
| 88 | ||
| 89 | private StageState initState( Stage stage ) { | |
| 90 | return new StageState( stage, getOptions().getState() ); | |
| 91 | } | |
| 92 | ||
| 93 | private void initStage( Stage stage ) { | |
| 94 | stage.getIcons().addAll( | |
| 95 | new Image( LOGO_16 ), | |
| 96 | new Image( LOGO_32 ), | |
| 97 | new Image( LOGO_128 ), | |
| 98 | new Image( LOGO_256 ), | |
| 99 | new Image( LOGO_512 ) ); | |
| 100 | stage.setTitle( getApplicationTitle() ); | |
| 101 | stage.setScene( getScene() ); | |
| 102 | } | |
| 103 | ||
| 104 | private void initAlertService() { | |
| 105 | final AlertService service = Services.load( AlertService.class ); | |
| 106 | service.setWindow( getScene().getWindow() ); | |
| 107 | } | |
| 108 | ||
| 109 | private Scene getScene() { | |
| 110 | return getMainWindow().getScene(); | |
| 111 | } | |
| 112 | ||
| 113 | private MainWindow getMainWindow() { | |
| 114 | return this.mainWindow; | |
| 115 | } | |
| 116 | ||
| 117 | private static Application getApplication() { | |
| 118 | return app; | |
| 119 | } | |
| 120 | ||
| 121 | public static void showDocument( String uri ) { | |
| 122 | getApplication().getHostServices().showDocument( uri ); | |
| 123 | } | |
| 124 | } | |
| 1 | 125 |
| 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; | |
| 29 | ||
| 30 | import static com.scrivenvar.Constants.LOGO_32; | |
| 31 | import static com.scrivenvar.Messages.get; | |
| 32 | import com.scrivenvar.definition.DefinitionPane; | |
| 33 | import com.scrivenvar.editor.EditorPane; | |
| 34 | import com.scrivenvar.editor.MarkdownEditorPane; | |
| 35 | import com.scrivenvar.editor.VariableNameInjector; | |
| 36 | import com.scrivenvar.options.OptionsDialog; | |
| 37 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 38 | import com.scrivenvar.processors.HTMLPreviewProcessor; | |
| 39 | import com.scrivenvar.processors.MarkdownCaretInsertionProcessor; | |
| 40 | import com.scrivenvar.processors.MarkdownCaretReplacementProcessor; | |
| 41 | import com.scrivenvar.processors.MarkdownProcessor; | |
| 42 | import com.scrivenvar.processors.Processor; | |
| 43 | import com.scrivenvar.processors.TextChangeProcessor; | |
| 44 | import com.scrivenvar.processors.VariableProcessor; | |
| 45 | import com.scrivenvar.service.Options; | |
| 46 | import com.scrivenvar.util.Action; | |
| 47 | import com.scrivenvar.util.ActionUtils; | |
| 48 | import static com.scrivenvar.util.StageState.K_PANE_SPLIT_DEFINITION; | |
| 49 | import static com.scrivenvar.util.StageState.K_PANE_SPLIT_EDITOR; | |
| 50 | import com.scrivenvar.yaml.YamlParser; | |
| 51 | import com.scrivenvar.yaml.YamlTreeAdapter; | |
| 52 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.BOLD; | |
| 53 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.CODE; | |
| 54 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_ALT; | |
| 55 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_CODE_ALT; | |
| 56 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FLOPPY_ALT; | |
| 57 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FOLDER_OPEN_ALT; | |
| 58 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.HEADER; | |
| 59 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.ITALIC; | |
| 60 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LINK; | |
| 61 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_OL; | |
| 62 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_UL; | |
| 63 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.PICTURE_ALT; | |
| 64 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.QUOTE_LEFT; | |
| 65 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.REPEAT; | |
| 66 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.STRIKETHROUGH; | |
| 67 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.UNDO; | |
| 68 | import java.io.IOException; | |
| 69 | import java.io.InputStream; | |
| 70 | import java.util.Map; | |
| 71 | import java.util.function.Function; | |
| 72 | import java.util.prefs.Preferences; | |
| 73 | import javafx.beans.binding.Bindings; | |
| 74 | import javafx.beans.binding.BooleanBinding; | |
| 75 | import javafx.beans.property.BooleanProperty; | |
| 76 | import javafx.beans.property.SimpleBooleanProperty; | |
| 77 | import javafx.beans.value.ObservableBooleanValue; | |
| 78 | import javafx.beans.value.ObservableValue; | |
| 79 | import javafx.collections.ListChangeListener.Change; | |
| 80 | import javafx.collections.ObservableList; | |
| 81 | import javafx.event.Event; | |
| 82 | import javafx.scene.Node; | |
| 83 | import javafx.scene.Scene; | |
| 84 | import javafx.scene.control.Alert; | |
| 85 | import javafx.scene.control.Alert.AlertType; | |
| 86 | import javafx.scene.control.Menu; | |
| 87 | import javafx.scene.control.MenuBar; | |
| 88 | import javafx.scene.control.SplitPane; | |
| 89 | import javafx.scene.control.Tab; | |
| 90 | import javafx.scene.control.ToolBar; | |
| 91 | import javafx.scene.control.TreeView; | |
| 92 | import javafx.scene.image.Image; | |
| 93 | import javafx.scene.image.ImageView; | |
| 94 | import static javafx.scene.input.KeyCode.ESCAPE; | |
| 95 | import javafx.scene.input.KeyEvent; | |
| 96 | import static javafx.scene.input.KeyEvent.CHAR_UNDEFINED; | |
| 97 | import static javafx.scene.input.KeyEvent.KEY_PRESSED; | |
| 98 | import javafx.scene.layout.BorderPane; | |
| 99 | import javafx.scene.layout.VBox; | |
| 100 | import javafx.stage.Window; | |
| 101 | import javafx.stage.WindowEvent; | |
| 102 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 103 | ||
| 104 | /** | |
| 105 | * Main window containing a tab pane in the center for file editors. | |
| 106 | * | |
| 107 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 108 | */ | |
| 109 | public class MainWindow { | |
| 110 | ||
| 111 | private final Options options = Services.load( Options.class ); | |
| 112 | ||
| 113 | private Scene scene; | |
| 114 | ||
| 115 | private TreeView<String> treeView; | |
| 116 | private FileEditorTabPane fileEditorPane; | |
| 117 | private DefinitionPane definitionPane; | |
| 118 | ||
| 119 | private VariableNameInjector variableNameInjector; | |
| 120 | ||
| 121 | private YamlTreeAdapter yamlTreeAdapter; | |
| 122 | private YamlParser yamlParser; | |
| 123 | ||
| 124 | private MenuBar menuBar; | |
| 125 | ||
| 126 | public MainWindow() { | |
| 127 | initLayout(); | |
| 128 | initVariableNameInjector(); | |
| 129 | } | |
| 130 | ||
| 131 | private void initLayout() { | |
| 132 | final SplitPane splitPane = new SplitPane( | |
| 133 | getDefinitionPane().getNode(), | |
| 134 | getFileEditorPane().getNode() ); | |
| 135 | ||
| 136 | splitPane.setDividerPositions( | |
| 137 | getFloat( K_PANE_SPLIT_DEFINITION, .05f ), | |
| 138 | getFloat( K_PANE_SPLIT_EDITOR, .95f ) ); | |
| 139 | ||
| 140 | // See: http://broadlyapplicable.blogspot.ca/2015/03/javafx-capture-restore-splitpane.html | |
| 141 | final BorderPane borderPane = new BorderPane(); | |
| 142 | borderPane.setPrefSize( 1024, 800 ); | |
| 143 | borderPane.setTop( createMenuBar() ); | |
| 144 | borderPane.setCenter( splitPane ); | |
| 145 | ||
| 146 | final Scene appScene = new Scene( borderPane ); | |
| 147 | setScene( appScene ); | |
| 148 | appScene.getStylesheets().add( Constants.STYLESHEET_PREVIEW ); | |
| 149 | appScene.windowProperty().addListener( | |
| 150 | (observable, oldWindow, newWindow) -> { | |
| 151 | newWindow.setOnCloseRequest( e -> { | |
| 152 | if( !getFileEditorPane().closeAllEditors() ) { | |
| 153 | e.consume(); | |
| 154 | } | |
| 155 | } ); | |
| 156 | ||
| 157 | // Workaround JavaFX bug: deselect menubar if window loses focus. | |
| 158 | newWindow.focusedProperty().addListener( | |
| 159 | (obs, oldFocused, newFocused) -> { | |
| 160 | if( !newFocused ) { | |
| 161 | // Send an ESC key event to the menubar | |
| 162 | this.menuBar.fireEvent( | |
| 163 | new KeyEvent( | |
| 164 | KEY_PRESSED, CHAR_UNDEFINED, "", ESCAPE, | |
| 165 | false, false, false, false ) ); | |
| 166 | } | |
| 167 | } ); | |
| 168 | } ); | |
| 169 | } | |
| 170 | ||
| 171 | private void initVariableNameInjector() { | |
| 172 | setVariableNameInjector( new VariableNameInjector( | |
| 173 | getFileEditorPane(), | |
| 174 | getDefinitionPane() ) | |
| 175 | ); | |
| 176 | } | |
| 177 | ||
| 178 | private Window getWindow() { | |
| 179 | return getScene().getWindow(); | |
| 180 | } | |
| 181 | ||
| 182 | public Scene getScene() { | |
| 183 | return this.scene; | |
| 184 | } | |
| 185 | ||
| 186 | private void setScene( Scene scene ) { | |
| 187 | this.scene = scene; | |
| 188 | } | |
| 189 | ||
| 190 | /** | |
| 191 | * Creates a boolean property that is bound to another boolean value of the | |
| 192 | * active editor. | |
| 193 | */ | |
| 194 | private BooleanProperty createActiveBooleanProperty( | |
| 195 | final Function<FileEditorTab, ObservableBooleanValue> func ) { | |
| 196 | ||
| 197 | final BooleanProperty b = new SimpleBooleanProperty(); | |
| 198 | final FileEditorTab tab = getActiveFileEditor(); | |
| 199 | ||
| 200 | if( tab != null ) { | |
| 201 | b.bind( func.apply( tab ) ); | |
| 202 | } | |
| 203 | ||
| 204 | getFileEditorPane().activeFileEditorProperty().addListener( | |
| 205 | (observable, oldFileEditor, newFileEditor) -> { | |
| 206 | b.unbind(); | |
| 207 | ||
| 208 | if( newFileEditor != null ) { | |
| 209 | b.bind( func.apply( newFileEditor ) ); | |
| 210 | } else { | |
| 211 | b.set( false ); | |
| 212 | } | |
| 213 | } ); | |
| 214 | ||
| 215 | return b; | |
| 216 | } | |
| 217 | ||
| 218 | //---- File actions ------------------------------------------------------- | |
| 219 | private void fileNew() { | |
| 220 | getFileEditorPane().newEditor(); | |
| 221 | } | |
| 222 | ||
| 223 | private void fileOpen() { | |
| 224 | getFileEditorPane().openFileDialog(); | |
| 225 | } | |
| 226 | ||
| 227 | private void fileClose() { | |
| 228 | getFileEditorPane().closeEditor( getActiveFileEditor(), true ); | |
| 229 | } | |
| 230 | ||
| 231 | private void fileCloseAll() { | |
| 232 | getFileEditorPane().closeAllEditors(); | |
| 233 | } | |
| 234 | ||
| 235 | private void fileSave() { | |
| 236 | getFileEditorPane().saveEditor( getActiveFileEditor() ); | |
| 237 | } | |
| 238 | ||
| 239 | private void fileSaveAll() { | |
| 240 | getFileEditorPane().saveAllEditors(); | |
| 241 | } | |
| 242 | ||
| 243 | private void fileExit() { | |
| 244 | final Window window = getWindow(); | |
| 245 | Event.fireEvent( window, | |
| 246 | new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) ); | |
| 247 | } | |
| 248 | ||
| 249 | //---- Tools actions ------------------------------------------------------ | |
| 250 | private void toolsOptions() { | |
| 251 | new OptionsDialog( getWindow() ).showAndWait(); | |
| 252 | } | |
| 253 | ||
| 254 | //---- Help actions ------------------------------------------------------- | |
| 255 | private void helpAbout() { | |
| 256 | Alert alert = new Alert( AlertType.INFORMATION ); | |
| 257 | alert.setTitle( Messages.get( "Dialog.about.title" ) ); | |
| 258 | alert.setHeaderText( Messages.get( "Dialog.about.header" ) ); | |
| 259 | alert.setContentText( Messages.get( "Dialog.about.content" ) ); | |
| 260 | alert.setGraphic( new ImageView( new Image( LOGO_32 ) ) ); | |
| 261 | alert.initOwner( getWindow() ); | |
| 262 | ||
| 263 | alert.showAndWait(); | |
| 264 | } | |
| 265 | ||
| 266 | private FileEditorTabPane getFileEditorPane() { | |
| 267 | if( this.fileEditorPane == null ) { | |
| 268 | this.fileEditorPane = createFileEditorPane(); | |
| 269 | } | |
| 270 | ||
| 271 | return this.fileEditorPane; | |
| 272 | } | |
| 273 | ||
| 274 | private FileEditorTabPane createFileEditorPane() { | |
| 275 | // Create an editor pane to hold file editor tabs. | |
| 276 | final FileEditorTabPane editorPane = new FileEditorTabPane(); | |
| 277 | ||
| 278 | // Make sure the text processor kicks off when new files are opened. | |
| 279 | final ObservableList<Tab> tabs = editorPane.getTabs(); | |
| 280 | ||
| 281 | tabs.addListener( (Change<? extends Tab> change) -> { | |
| 282 | while( change.next() ) { | |
| 283 | if( change.wasAdded() ) { | |
| 284 | // Multiple tabs can be added simultaneously. | |
| 285 | for( final Tab tab : change.getAddedSubList() ) { | |
| 286 | addListener( (FileEditorTab)tab ); | |
| 287 | } | |
| 288 | } | |
| 289 | } | |
| 290 | } ); | |
| 291 | ||
| 292 | // After the processors are in place, restore the previously closed | |
| 293 | // tabs. Adding them will trigger the change event, above. | |
| 294 | editorPane.restoreState(); | |
| 295 | ||
| 296 | return editorPane; | |
| 297 | } | |
| 298 | ||
| 299 | private MarkdownEditorPane getActiveEditor() { | |
| 300 | return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane()); | |
| 301 | } | |
| 302 | ||
| 303 | private FileEditorTab getActiveFileEditor() { | |
| 304 | return getFileEditorPane().getActiveFileEditor(); | |
| 305 | } | |
| 306 | ||
| 307 | /** | |
| 308 | * Listens for changes to tabs and their text editors. | |
| 309 | * | |
| 310 | * @see https://github.com/DaveJarvis/scrivenvar/issues/17 | |
| 311 | * @see https://github.com/DaveJarvis/scrivenvar/issues/18 | |
| 312 | * | |
| 313 | * @param tab The file editor tab that contains a text editor. | |
| 314 | */ | |
| 315 | private void addListener( FileEditorTab tab ) { | |
| 316 | final HTMLPreviewPane previewPane = tab.getPreviewPane(); | |
| 317 | final EditorPane editorPanel = tab.getEditorPane(); | |
| 318 | final StyleClassedTextArea editor = editorPanel.getEditor(); | |
| 319 | ||
| 320 | // TODO: Use a factory based on the filename extension. The default | |
| 321 | // extension will be for a markdown file (e.g., on file new). | |
| 322 | final Processor<String> hpp = new HTMLPreviewProcessor( previewPane ); | |
| 323 | final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp ); | |
| 324 | final Processor<String> mp = new MarkdownProcessor( mcrp ); | |
| 325 | final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, editor ); | |
| 326 | final Processor<String> vnp = new VariableProcessor( mcip, getResolvedMap() ); | |
| 327 | final TextChangeProcessor tp = new TextChangeProcessor( vnp ); | |
| 328 | ||
| 329 | editorPanel.addChangeListener( tp ); | |
| 330 | editorPanel.addCaretParagraphListener( | |
| 331 | (final ObservableValue<? extends Integer> observable, | |
| 332 | final Integer oldValue, final Integer newValue) -> { | |
| 333 | ||
| 334 | // Kick off the processing chain at the variable processor when the | |
| 335 | // cursor changes paragraphs. This might cause some slight duplication | |
| 336 | // when the Enter key is pressed. | |
| 337 | vnp.processChain( editor.getText() ); | |
| 338 | } ); | |
| 339 | } | |
| 340 | ||
| 341 | protected DefinitionPane createDefinitionPane() { | |
| 342 | return new DefinitionPane( getTreeView() ); | |
| 343 | } | |
| 344 | ||
| 345 | private DefinitionPane getDefinitionPane() { | |
| 346 | if( this.definitionPane == null ) { | |
| 347 | this.definitionPane = createDefinitionPane(); | |
| 348 | } | |
| 349 | ||
| 350 | return this.definitionPane; | |
| 351 | } | |
| 352 | ||
| 353 | public MenuBar getMenuBar() { | |
| 354 | return menuBar; | |
| 355 | } | |
| 356 | ||
| 357 | public void setMenuBar( MenuBar menuBar ) { | |
| 358 | this.menuBar = menuBar; | |
| 359 | } | |
| 360 | ||
| 361 | public VariableNameInjector getVariableNameInjector() { | |
| 362 | return this.variableNameInjector; | |
| 363 | } | |
| 364 | ||
| 365 | public void setVariableNameInjector( VariableNameInjector variableNameInjector ) { | |
| 366 | this.variableNameInjector = variableNameInjector; | |
| 367 | } | |
| 368 | ||
| 369 | private float getFloat( final String key, final float defaultValue ) { | |
| 370 | return getPreferences().getFloat( key, defaultValue ); | |
| 371 | } | |
| 372 | ||
| 373 | private Preferences getPreferences() { | |
| 374 | return getOptions().getState(); | |
| 375 | } | |
| 376 | ||
| 377 | private Options getOptions() { | |
| 378 | return this.options; | |
| 379 | } | |
| 380 | ||
| 381 | private synchronized TreeView<String> getTreeView() throws RuntimeException { | |
| 382 | if( this.treeView == null ) { | |
| 383 | try { | |
| 384 | this.treeView = createTreeView(); | |
| 385 | } catch( IOException ex ) { | |
| 386 | ||
| 387 | // TODO: Pop an error message. | |
| 388 | throw new RuntimeException( ex ); | |
| 389 | } | |
| 390 | } | |
| 391 | ||
| 392 | return this.treeView; | |
| 393 | } | |
| 394 | ||
| 395 | private InputStream asStream( final String resource ) { | |
| 396 | return getClass().getResourceAsStream( resource ); | |
| 397 | } | |
| 398 | ||
| 399 | private TreeView<String> createTreeView() throws IOException { | |
| 400 | // TODO: Associate variable file with path to current file. | |
| 401 | return getYamlTreeAdapter().adapt( | |
| 402 | asStream( "/com/scrivenvar/variables.yaml" ), | |
| 403 | get( "Pane.defintion.node.root.title" ) | |
| 404 | ); | |
| 405 | } | |
| 406 | ||
| 407 | private Map<String, String> getResolvedMap() { | |
| 408 | return getYamlParser().createResolvedMap(); | |
| 409 | } | |
| 410 | ||
| 411 | private YamlTreeAdapter getYamlTreeAdapter() { | |
| 412 | if( this.yamlTreeAdapter == null ) { | |
| 413 | setYamlTreeAdapter( new YamlTreeAdapter( getYamlParser() ) ); | |
| 414 | } | |
| 415 | ||
| 416 | return this.yamlTreeAdapter; | |
| 417 | } | |
| 418 | ||
| 419 | private void setYamlTreeAdapter( final YamlTreeAdapter yamlTreeAdapter ) { | |
| 420 | this.yamlTreeAdapter = yamlTreeAdapter; | |
| 421 | } | |
| 422 | ||
| 423 | private YamlParser getYamlParser() { | |
| 424 | if( this.yamlParser == null ) { | |
| 425 | setYamlParser( new YamlParser() ); | |
| 426 | } | |
| 427 | ||
| 428 | return this.yamlParser; | |
| 429 | } | |
| 430 | ||
| 431 | private void setYamlParser( final YamlParser yamlParser ) { | |
| 432 | this.yamlParser = yamlParser; | |
| 433 | } | |
| 434 | ||
| 435 | private Node createMenuBar() { | |
| 436 | final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull(); | |
| 437 | ||
| 438 | // File actions | |
| 439 | Action fileNewAction = new Action( Messages.get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() ); | |
| 440 | Action fileOpenAction = new Action( Messages.get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() ); | |
| 441 | Action fileCloseAction = new Action( Messages.get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull ); | |
| 442 | Action fileCloseAllAction = new Action( Messages.get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull ); | |
| 443 | Action fileSaveAction = new Action( Messages.get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(), | |
| 444 | createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() ); | |
| 445 | Action fileSaveAllAction = new Action( Messages.get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(), | |
| 446 | Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) ); | |
| 447 | Action fileExitAction = new Action( Messages.get( "Main.menu.file.exit" ), null, null, e -> fileExit() ); | |
| 448 | ||
| 449 | // Edit actions | |
| 450 | Action editUndoAction = new Action( Messages.get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO, | |
| 451 | e -> getActiveEditor().undo(), | |
| 452 | createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() ); | |
| 453 | Action editRedoAction = new Action( Messages.get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT, | |
| 454 | e -> getActiveEditor().redo(), | |
| 455 | createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() ); | |
| 456 | ||
| 457 | // Insert actions | |
| 458 | Action insertBoldAction = new Action( Messages.get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD, | |
| 459 | e -> getActiveEditor().surroundSelection( "**", "**" ), | |
| 460 | activeFileEditorIsNull ); | |
| 461 | Action insertItalicAction = new Action( Messages.get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC, | |
| 462 | e -> getActiveEditor().surroundSelection( "*", "*" ), | |
| 463 | activeFileEditorIsNull ); | |
| 464 | Action insertStrikethroughAction = new Action( Messages.get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH, | |
| 465 | e -> getActiveEditor().surroundSelection( "~~", "~~" ), | |
| 466 | activeFileEditorIsNull ); | |
| 467 | Action insertBlockquoteAction = new Action( Messages.get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac | |
| 468 | e -> getActiveEditor().surroundSelection( "\n\n> ", "" ), | |
| 469 | activeFileEditorIsNull ); | |
| 470 | Action insertCodeAction = new Action( Messages.get( "Main.menu.insert.code" ), "Shortcut+K", CODE, | |
| 471 | e -> getActiveEditor().surroundSelection( "`", "`" ), | |
| 472 | activeFileEditorIsNull ); | |
| 473 | Action insertFencedCodeBlockAction = new Action( Messages.get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT, | |
| 474 | e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", Messages.get( "Main.menu.insert.fenced_code_block.prompt" ) ), | |
| 475 | activeFileEditorIsNull ); | |
| 476 | ||
| 477 | Action insertLinkAction = new Action( Messages.get( "Main.menu.insert.link" ), "Shortcut+L", LINK, | |
| 478 | e -> getActiveEditor().insertLink(), | |
| 479 | activeFileEditorIsNull ); | |
| 480 | Action insertImageAction = new Action( Messages.get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT, | |
| 481 | e -> getActiveEditor().insertImage(), | |
| 482 | activeFileEditorIsNull ); | |
| 483 | ||
| 484 | final Action[] headers = new Action[ 6 ]; | |
| 485 | ||
| 486 | // Insert header actions (H1 ... H6) | |
| 487 | for( int i = 1; i <= 6; i++ ) { | |
| 488 | final String hashes = new String( new char[ i ] ).replace( "\0", "#" ); | |
| 489 | final String markup = String.format( "\n\n%s ", hashes ); | |
| 490 | final String text = Messages.get( "Main.menu.insert.header_" + i ); | |
| 491 | final String accelerator = "Shortcut+" + i; | |
| 492 | final String prompt = Messages.get( "Main.menu.insert.header_" + i + ".prompt" ); | |
| 493 | ||
| 494 | headers[ i - 1 ] = new Action( text, accelerator, HEADER, | |
| 495 | e -> getActiveEditor().surroundSelection( markup, "", prompt ), | |
| 496 | activeFileEditorIsNull ); | |
| 497 | } | |
| 498 | ||
| 499 | Action insertUnorderedListAction = new Action( Messages.get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL, | |
| 500 | e -> getActiveEditor().surroundSelection( "\n\n* ", "" ), | |
| 501 | activeFileEditorIsNull ); | |
| 502 | Action insertOrderedListAction = new Action( Messages.get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL, | |
| 503 | e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ), | |
| 504 | activeFileEditorIsNull ); | |
| 505 | Action insertHorizontalRuleAction = new Action( Messages.get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null, | |
| 506 | e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ), | |
| 507 | activeFileEditorIsNull ); | |
| 508 | ||
| 509 | // Tools actions | |
| 510 | Action toolsOptionsAction = new Action( Messages.get( "Main.menu.tools.options" ), "Shortcut+,", null, e -> toolsOptions() ); | |
| 511 | ||
| 512 | // Help actions | |
| 513 | Action helpAboutAction = new Action( Messages.get( "Main.menu.help.about" ), null, null, e -> helpAbout() ); | |
| 514 | ||
| 515 | //---- MenuBar ---- | |
| 516 | Menu fileMenu = ActionUtils.createMenu( Messages.get( "Main.menu.file" ), | |
| 517 | fileNewAction, | |
| 518 | fileOpenAction, | |
| 519 | null, | |
| 520 | fileCloseAction, | |
| 521 | fileCloseAllAction, | |
| 522 | null, | |
| 523 | fileSaveAction, | |
| 524 | fileSaveAllAction, | |
| 525 | null, | |
| 526 | fileExitAction ); | |
| 527 | ||
| 528 | Menu editMenu = ActionUtils.createMenu( Messages.get( "Main.menu.edit" ), | |
| 529 | editUndoAction, | |
| 530 | editRedoAction ); | |
| 531 | ||
| 532 | Menu insertMenu = ActionUtils.createMenu( Messages.get( "Main.menu.insert" ), | |
| 533 | insertBoldAction, | |
| 534 | insertItalicAction, | |
| 535 | insertStrikethroughAction, | |
| 536 | insertBlockquoteAction, | |
| 537 | insertCodeAction, | |
| 538 | insertFencedCodeBlockAction, | |
| 539 | null, | |
| 540 | insertLinkAction, | |
| 541 | insertImageAction, | |
| 542 | null, | |
| 543 | headers[ 0 ], | |
| 544 | headers[ 1 ], | |
| 545 | headers[ 2 ], | |
| 546 | headers[ 3 ], | |
| 547 | headers[ 4 ], | |
| 548 | headers[ 5 ], | |
| 549 | null, | |
| 550 | insertUnorderedListAction, | |
| 551 | insertOrderedListAction, | |
| 552 | insertHorizontalRuleAction ); | |
| 553 | ||
| 554 | Menu toolsMenu = ActionUtils.createMenu( Messages.get( "Main.menu.tools" ), | |
| 555 | toolsOptionsAction ); | |
| 556 | ||
| 557 | Menu helpMenu = ActionUtils.createMenu( Messages.get( "Main.menu.help" ), | |
| 558 | helpAboutAction ); | |
| 559 | ||
| 560 | menuBar = new MenuBar( fileMenu, editMenu, insertMenu, toolsMenu, helpMenu ); | |
| 561 | ||
| 562 | //---- ToolBar ---- | |
| 563 | ToolBar toolBar = ActionUtils.createToolBar( | |
| 564 | fileNewAction, | |
| 565 | fileOpenAction, | |
| 566 | fileSaveAction, | |
| 567 | null, | |
| 568 | editUndoAction, | |
| 569 | editRedoAction, | |
| 570 | null, | |
| 571 | insertBoldAction, | |
| 572 | insertItalicAction, | |
| 573 | insertBlockquoteAction, | |
| 574 | insertCodeAction, | |
| 575 | insertFencedCodeBlockAction, | |
| 576 | null, | |
| 577 | insertLinkAction, | |
| 578 | insertImageAction, | |
| 579 | null, | |
| 580 | headers[ 0 ], | |
| 581 | null, | |
| 582 | insertUnorderedListAction, | |
| 583 | insertOrderedListAction ); | |
| 584 | ||
| 585 | return new VBox( menuBar, toolBar ); | |
| 586 | } | |
| 587 | } | |
| 1 | 588 |
| 1 | /* | |
| 2 | * Copyright (c) 2016 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 | * * 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 static com.scrivenvar.Constants.BUNDLE_NAME; | |
| 30 | import java.text.MessageFormat; | |
| 31 | import java.util.ResourceBundle; | |
| 32 | import java.util.Stack; | |
| 33 | ||
| 34 | /** | |
| 35 | * Recursively resolves message properties. Property values can refer to other | |
| 36 | * properties using a <code>${var}</code> syntax. | |
| 37 | * | |
| 38 | * @author Karl Tauber, Dave Jarvis | |
| 39 | */ | |
| 40 | public class Messages { | |
| 41 | ||
| 42 | private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle( BUNDLE_NAME ); | |
| 43 | ||
| 44 | private Messages() { | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Return the value of a resource bundle value after having resolved any | |
| 49 | * references to other bundle variables. | |
| 50 | * | |
| 51 | * @param props The bundle containing resolvable properties. | |
| 52 | * @param s The value for a key to resolve. | |
| 53 | * | |
| 54 | * @return The value of the key with all references recursively dereferenced. | |
| 55 | */ | |
| 56 | private static String resolve( ResourceBundle props, String s ) { | |
| 57 | final int len = s.length(); | |
| 58 | final Stack<StringBuilder> stack = new Stack<>(); | |
| 59 | ||
| 60 | StringBuilder sb = new StringBuilder( 256 ); | |
| 61 | boolean open = false; | |
| 62 | ||
| 63 | for( int i = 0; i < len; i++ ) { | |
| 64 | final char c = s.charAt( i ); | |
| 65 | ||
| 66 | switch( c ) { | |
| 67 | case '$': { | |
| 68 | if( i + 1 < len && s.charAt( i + 1 ) == '{' ) { | |
| 69 | stack.push( sb ); | |
| 70 | sb = new StringBuilder( 256 ); | |
| 71 | i++; | |
| 72 | open = true; | |
| 73 | } | |
| 74 | ||
| 75 | break; | |
| 76 | } | |
| 77 | ||
| 78 | case '}': { | |
| 79 | if( open ) { | |
| 80 | open = false; | |
| 81 | final String name = sb.toString(); | |
| 82 | ||
| 83 | sb = stack.pop(); | |
| 84 | sb.append( props.getString( name ) ); | |
| 85 | break; | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | default: { | |
| 90 | sb.append( c ); | |
| 91 | break; | |
| 92 | } | |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | if( open ) { | |
| 97 | throw new IllegalArgumentException( "missing '}'" ); | |
| 98 | } | |
| 99 | ||
| 100 | return sb.toString(); | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Returns the value for a key from the message bundle. | |
| 105 | * | |
| 106 | * @param key Retrieve the value for this key. | |
| 107 | * | |
| 108 | * @return The value for the key. | |
| 109 | */ | |
| 110 | public static String get( String key ) { | |
| 111 | String result; | |
| 112 | ||
| 113 | try { | |
| 114 | result = resolve( RESOURCE_BUNDLE, RESOURCE_BUNDLE.getString( key ) ); | |
| 115 | } catch( Exception e ) { | |
| 116 | ||
| 117 | // Instead of crashing, launch the application and show the resource | |
| 118 | // name. | |
| 119 | result = key; | |
| 120 | } | |
| 121 | ||
| 122 | return result; | |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * Returns the value for a key from the message bundle with the arguments | |
| 127 | * replacing <code>{#}</code> place holders. | |
| 128 | * | |
| 129 | * @param key Retrieve the value for this key. | |
| 130 | * @param args The values to substitute for place holders. | |
| 131 | * | |
| 132 | * @return The value for the key. | |
| 133 | */ | |
| 134 | public static String get( String key, Object... args ) { | |
| 135 | return MessageFormat.format( get( key ), args ); | |
| 136 | } | |
| 137 | } | |
| 1 | 138 |
| 1 | /* | |
| 2 | * Copyright 2016 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.ServiceLoader; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for loading services. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public class Services { | |
| 38 | ||
| 39 | /** | |
| 40 | * Loads a service based on its interface definition. | |
| 41 | * | |
| 42 | * @param <T> The service to load. | |
| 43 | * @param api The interface definition for the service. | |
| 44 | * | |
| 45 | * @return A class that implements the interface. | |
| 46 | */ | |
| 47 | public static <T> T load( Class<T> api ) { | |
| 48 | final ServiceLoader<T> services = ServiceLoader.load( api ); | |
| 49 | T result = null; | |
| 50 | ||
| 51 | for( T service : services ) { | |
| 52 | result = service; | |
| 53 | ||
| 54 | if( result != null ) { | |
| 55 | break; | |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | if( result == null ) { | |
| 60 | throw new RuntimeException( "No implementation for: " + api ); | |
| 61 | } | |
| 62 | ||
| 63 | return result; | |
| 64 | } | |
| 65 | } | |
| 1 | 66 |
| 1 | /* | |
| 2 | * Copyright 2016 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.definition.DefinitionPane; | |
| 31 | import static javafx.application.Application.launch; | |
| 32 | import javafx.scene.control.TreeItem; | |
| 33 | import javafx.scene.control.TreeView; | |
| 34 | import javafx.stage.Stage; | |
| 35 | ||
| 36 | /** | |
| 37 | * TestDefinitionPane application for debugging. | |
| 38 | */ | |
| 39 | public final class TestDefinitionPane extends TestHarness { | |
| 40 | /** | |
| 41 | * Application entry point. | |
| 42 | * | |
| 43 | * @param stage The primary application stage. | |
| 44 | * | |
| 45 | * @throws Exception Could not read configuration file. | |
| 46 | */ | |
| 47 | @Override | |
| 48 | public void start( final Stage stage ) throws Exception { | |
| 49 | super.start( stage ); | |
| 50 | ||
| 51 | TreeView<String> root = createTreeView(); | |
| 52 | DefinitionPane pane = createDefinitionPane( root ); | |
| 53 | ||
| 54 | test( pane, "language.ai.", "article" ); | |
| 55 | test( pane, "language.ai", "ai" ); | |
| 56 | test( pane, "l", "location" ); | |
| 57 | test( pane, "la", "language" ); | |
| 58 | test( pane, "c.p.n", "name" ); | |
| 59 | test( pane, "c.p.n.", "First" ); | |
| 60 | test( pane, "...", "c" ); | |
| 61 | test( pane, "foo", "c" ); | |
| 62 | test( pane, "foo.bar", "c" ); | |
| 63 | test( pane, "", "c" ); | |
| 64 | test( pane, "c", "protagonist" ); | |
| 65 | test( pane, "c.", "protagonist" ); | |
| 66 | test( pane, "c.p", "protagonist" ); | |
| 67 | test( pane, "c.protagonist", "protagonist" ); | |
| 68 | ||
| 69 | System.exit( 0 ); | |
| 70 | } | |
| 71 | ||
| 72 | private void test( DefinitionPane pane, String path, String value ) { | |
| 73 | System.out.println( "---------------------------" ); | |
| 74 | System.out.println( "Find Path: '" + path + "'" ); | |
| 75 | final TreeItem<String> node = pane.findNode( path ); | |
| 76 | System.out.println( "Path Node: " + node ); | |
| 77 | System.out.println( "Node Val : " + node.getValue() ); | |
| 78 | } | |
| 79 | ||
| 80 | public static void main( String[] args ) { | |
| 81 | launch( args ); | |
| 82 | } | |
| 83 | } | |
| 1 | 84 |
| 1 | /* | |
| 2 | * Copyright 2016 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 static com.scrivenvar.Messages.get; | |
| 31 | import com.scrivenvar.definition.DefinitionPane; | |
| 32 | import com.scrivenvar.yaml.YamlParser; | |
| 33 | import com.scrivenvar.yaml.YamlTreeAdapter; | |
| 34 | import java.io.IOException; | |
| 35 | import java.io.InputStream; | |
| 36 | import javafx.application.Application; | |
| 37 | import javafx.scene.Scene; | |
| 38 | import javafx.scene.control.TreeView; | |
| 39 | import javafx.scene.layout.BorderPane; | |
| 40 | import javafx.stage.Stage; | |
| 41 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 42 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 43 | ||
| 44 | /** | |
| 45 | * TestDefinitionPane application for debugging and head-banging. | |
| 46 | */ | |
| 47 | public abstract class TestHarness extends Application { | |
| 48 | ||
| 49 | private static Application app; | |
| 50 | private Scene scene; | |
| 51 | ||
| 52 | /** | |
| 53 | * Application entry point. | |
| 54 | * | |
| 55 | * @param stage The primary application stage. | |
| 56 | * | |
| 57 | * @throws Exception Could not read configuration file. | |
| 58 | */ | |
| 59 | @Override | |
| 60 | public void start( final Stage stage ) throws Exception { | |
| 61 | initApplication(); | |
| 62 | initScene(); | |
| 63 | initStage( stage ); | |
| 64 | } | |
| 65 | ||
| 66 | protected TreeView<String> createTreeView() throws IOException { | |
| 67 | return new YamlTreeAdapter( new YamlParser() ).adapt( | |
| 68 | asStream( "/com/scrivenvar/variables.yaml" ), | |
| 69 | get( "Pane.defintion.node.root.title" ) | |
| 70 | ); | |
| 71 | } | |
| 72 | ||
| 73 | protected DefinitionPane createDefinitionPane( TreeView<String> root ) { | |
| 74 | return new DefinitionPane( root ); | |
| 75 | } | |
| 76 | ||
| 77 | private void initApplication() { | |
| 78 | app = this; | |
| 79 | } | |
| 80 | ||
| 81 | private void initScene() { | |
| 82 | final StyleClassedTextArea editor = new StyleClassedTextArea( false ); | |
| 83 | final VirtualizedScrollPane<StyleClassedTextArea> scrollPane = new VirtualizedScrollPane<>( editor ); | |
| 84 | ||
| 85 | final BorderPane borderPane = new BorderPane(); | |
| 86 | borderPane.setPrefSize( 1024, 800 ); | |
| 87 | borderPane.setCenter( scrollPane ); | |
| 88 | ||
| 89 | setScene( new Scene( borderPane ) ); | |
| 90 | } | |
| 91 | ||
| 92 | private void initStage( Stage stage ) { | |
| 93 | stage.setScene( getScene() ); | |
| 94 | } | |
| 95 | ||
| 96 | private Scene getScene() { | |
| 97 | return this.scene; | |
| 98 | } | |
| 99 | ||
| 100 | private void setScene( Scene scene ) { | |
| 101 | this.scene = scene; | |
| 102 | } | |
| 103 | ||
| 104 | private static Application getApplication() { | |
| 105 | return app; | |
| 106 | } | |
| 107 | ||
| 108 | public static void showDocument( String uri ) { | |
| 109 | getApplication().getHostServices().showDocument( uri ); | |
| 110 | } | |
| 111 | ||
| 112 | protected InputStream asStream( String resource ) { | |
| 113 | return getClass().getResourceAsStream( resource ); | |
| 114 | } | |
| 115 | } | |
| 1 | 116 |
| 1 | /* | |
| 2 | * Copyright 2016 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.ui.VariableTreeItem; | |
| 31 | import java.util.Collection; | |
| 32 | import java.util.HashMap; | |
| 33 | import java.util.Map; | |
| 34 | import static java.util.concurrent.ThreadLocalRandom.current; | |
| 35 | import java.util.concurrent.TimeUnit; | |
| 36 | import static java.util.concurrent.TimeUnit.DAYS; | |
| 37 | import static java.util.concurrent.TimeUnit.HOURS; | |
| 38 | import static java.util.concurrent.TimeUnit.MILLISECONDS; | |
| 39 | import static java.util.concurrent.TimeUnit.MINUTES; | |
| 40 | import static java.util.concurrent.TimeUnit.NANOSECONDS; | |
| 41 | import static java.util.concurrent.TimeUnit.SECONDS; | |
| 42 | import static javafx.application.Application.launch; | |
| 43 | import javafx.scene.control.TreeItem; | |
| 44 | import javafx.scene.control.TreeView; | |
| 45 | import javafx.stage.Stage; | |
| 46 | import org.ahocorasick.trie.*; | |
| 47 | import org.ahocorasick.trie.Trie.TrieBuilder; | |
| 48 | import static org.apache.commons.lang.RandomStringUtils.randomNumeric; | |
| 49 | import org.apache.commons.lang.StringUtils; | |
| 50 | ||
| 51 | /** | |
| 52 | * Tests substituting variable definitions with their values in a swath of text. | |
| 53 | * | |
| 54 | * @author White Magic Software, Ltd. | |
| 55 | */ | |
| 56 | public class TestVariableNameProcessor extends TestHarness { | |
| 57 | ||
| 58 | private final static int TEXT_SIZE = 1000000; | |
| 59 | private final static int MATCHES_DIVISOR = 1000; | |
| 60 | ||
| 61 | private final static StringBuilder SOURCE | |
| 62 | = new StringBuilder( randomNumeric( TEXT_SIZE ) ); | |
| 63 | ||
| 64 | private final static boolean DEBUG = false; | |
| 65 | ||
| 66 | public TestVariableNameProcessor() { | |
| 67 | } | |
| 68 | ||
| 69 | @Override | |
| 70 | public void start( final Stage stage ) throws Exception { | |
| 71 | super.start( stage ); | |
| 72 | ||
| 73 | final TreeView<String> treeView = createTreeView(); | |
| 74 | final Map<String, String> definitions = new HashMap<>(); | |
| 75 | ||
| 76 | populate( treeView.getRoot(), definitions ); | |
| 77 | injectVariables( definitions ); | |
| 78 | ||
| 79 | final String text = SOURCE.toString(); | |
| 80 | ||
| 81 | show( text ); | |
| 82 | ||
| 83 | long duration = System.nanoTime(); | |
| 84 | ||
| 85 | // TODO: Test replaceEach (with intercoluated variables) and replaceEachRepeatedly | |
| 86 | // (without intercoluation). | |
| 87 | final String result = testBorAhoCorasick( text, definitions ); | |
| 88 | ||
| 89 | duration = System.nanoTime() - duration; | |
| 90 | ||
| 91 | show( result ); | |
| 92 | System.out.println( elapsed( duration ) ); | |
| 93 | ||
| 94 | System.exit( 0 ); | |
| 95 | } | |
| 96 | ||
| 97 | private void show( final String s ) { | |
| 98 | if( DEBUG ) { | |
| 99 | System.out.printf( "%s\n\n", s ); | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | private String testBorAhoCorasick( | |
| 104 | final String text, | |
| 105 | final Map<String, String> definitions ) { | |
| 106 | // Create a buffer sufficiently large that re-allocations are minimized. | |
| 107 | final StringBuilder sb = new StringBuilder( text.length() << 1 ); | |
| 108 | ||
| 109 | final TrieBuilder builder = Trie.builder(); | |
| 110 | builder.onlyWholeWords(); | |
| 111 | builder.removeOverlaps(); | |
| 112 | ||
| 113 | final String[] keys = keys( definitions ); | |
| 114 | ||
| 115 | for( final String key : keys ) { | |
| 116 | builder.addKeyword( key ); | |
| 117 | } | |
| 118 | ||
| 119 | final Trie trie = builder.build(); | |
| 120 | final Collection<Emit> emits = trie.parseText( text ); | |
| 121 | ||
| 122 | int prevIndex = 0; | |
| 123 | ||
| 124 | for( final Emit emit : emits ) { | |
| 125 | final int matchIndex = emit.getStart(); | |
| 126 | ||
| 127 | sb.append( text.substring( prevIndex, matchIndex ) ); | |
| 128 | sb.append( definitions.get( emit.getKeyword() ) ); | |
| 129 | prevIndex = emit.getEnd() + 1; | |
| 130 | } | |
| 131 | ||
| 132 | // Add the remainder of the string (contains no more matches). | |
| 133 | sb.append( text.substring( prevIndex ) ); | |
| 134 | ||
| 135 | return sb.toString(); | |
| 136 | } | |
| 137 | ||
| 138 | private String testStringUtils( | |
| 139 | final String text, final Map<String, String> definitions ) { | |
| 140 | final String[] keys = keys( definitions ); | |
| 141 | final String[] values = values( definitions ); | |
| 142 | ||
| 143 | return StringUtils.replaceEach( text, keys, values ); | |
| 144 | } | |
| 145 | ||
| 146 | private String[] keys( final Map<String, String> definitions ) { | |
| 147 | final int size = definitions.size(); | |
| 148 | return definitions.keySet().toArray( new String[ size ] ); | |
| 149 | } | |
| 150 | ||
| 151 | private String[] values( final Map<String, String> definitions ) { | |
| 152 | final int size = definitions.size(); | |
| 153 | return definitions.values().toArray( new String[ size ] ); | |
| 154 | } | |
| 155 | ||
| 156 | /** | |
| 157 | * Decomposes a period of time into days, hours, minutes, seconds, | |
| 158 | * milliseconds, and nanoseconds. | |
| 159 | * | |
| 160 | * @param duration Time in nanoseconds. | |
| 161 | * | |
| 162 | * @return A non-null, comma-separated string (without newline). | |
| 163 | */ | |
| 164 | public String elapsed( long duration ) { | |
| 165 | final TimeUnit scale = NANOSECONDS; | |
| 166 | ||
| 167 | long days = scale.toDays( duration ); | |
| 168 | duration -= DAYS.toMillis( days ); | |
| 169 | long hours = scale.toHours( duration ); | |
| 170 | duration -= HOURS.toMillis( hours ); | |
| 171 | long minutes = scale.toMinutes( duration ); | |
| 172 | duration -= MINUTES.toMillis( minutes ); | |
| 173 | long seconds = scale.toSeconds( duration ); | |
| 174 | duration -= SECONDS.toMillis( seconds ); | |
| 175 | long millis = scale.toMillis( duration ); | |
| 176 | duration -= MILLISECONDS.toMillis( seconds ); | |
| 177 | long nanos = scale.toNanos( duration ); | |
| 178 | ||
| 179 | return String.format( | |
| 180 | "%d days, %d hours, %d minutes, %d seconds, %d millis, %d nanos", | |
| 181 | days, hours, minutes, seconds, millis, nanos | |
| 182 | ); | |
| 183 | } | |
| 184 | ||
| 185 | private void injectVariables( final Map<String, String> definitions ) { | |
| 186 | for( int i = (SOURCE.length() / MATCHES_DIVISOR) + 1; i > 0; i-- ) { | |
| 187 | final int r = current().nextInt( 1, SOURCE.length() ); | |
| 188 | SOURCE.insert( r, randomKey( definitions ) ); | |
| 189 | } | |
| 190 | } | |
| 191 | ||
| 192 | private String randomKey( final Map<String, String> map ) { | |
| 193 | final Object[] keys = map.keySet().toArray(); | |
| 194 | final int r = current().nextInt( keys.length ); | |
| 195 | return keys[ r ].toString(); | |
| 196 | } | |
| 197 | ||
| 198 | private void populate( final TreeItem<String> parent, final Map<String, String> map ) { | |
| 199 | for( final TreeItem<String> child : parent.getChildren() ) { | |
| 200 | if( child.isLeaf() ) { | |
| 201 | final String key = asDefinition( ((VariableTreeItem<String>)child).toPath() ); | |
| 202 | final String value = child.getValue(); | |
| 203 | ||
| 204 | map.put( key, value ); | |
| 205 | } else { | |
| 206 | populate( child, map ); | |
| 207 | } | |
| 208 | } | |
| 209 | } | |
| 210 | ||
| 211 | private String asDefinition( final String key ) { | |
| 212 | return "$" + key + "$"; | |
| 213 | } | |
| 214 | ||
| 215 | public static void main( String[] args ) { | |
| 216 | launch( args ); | |
| 217 | } | |
| 218 | } | |
| 1 | 219 |
| 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 | package com.scrivenvar.controls; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import javafx.event.ActionEvent; | |
| 32 | import javafx.scene.control.Tooltip; | |
| 33 | import javafx.stage.DirectoryChooser; | |
| 34 | import com.scrivenvar.Messages; | |
| 35 | import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; | |
| 36 | import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory; | |
| 37 | ||
| 38 | /** | |
| 39 | * Button that opens a directory chooser to select a local directory for a URL in markdown. | |
| 40 | * | |
| 41 | * @author Karl Tauber | |
| 42 | */ | |
| 43 | public class BrowseDirectoryButton | |
| 44 | extends BrowseFileButton | |
| 45 | { | |
| 46 | public BrowseDirectoryButton() { | |
| 47 | setGraphic(FontAwesomeIconFactory.get().createIcon(FontAwesomeIcon.FOLDER_ALT, "1.2em")); | |
| 48 | setTooltip(new Tooltip(Messages.get("BrowseDirectoryButton.tooltip"))); | |
| 49 | } | |
| 50 | ||
| 51 | @Override | |
| 52 | protected void browse(ActionEvent e) { | |
| 53 | DirectoryChooser directoryChooser = new DirectoryChooser(); | |
| 54 | directoryChooser.setTitle(Messages.get("BrowseDirectoryButton.chooser.title")); | |
| 55 | directoryChooser.setInitialDirectory(getInitialDirectory()); | |
| 56 | File result = directoryChooser.showDialog(getScene().getWindow()); | |
| 57 | if (result != null) | |
| 58 | updateUrl(result); | |
| 59 | } | |
| 60 | } | |
| 1 | 61 |
| 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 | package com.scrivenvar.controls; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.nio.file.Path; | |
| 32 | import java.util.ArrayList; | |
| 33 | import java.util.List; | |
| 34 | import javafx.beans.property.ObjectProperty; | |
| 35 | import javafx.beans.property.SimpleObjectProperty; | |
| 36 | import javafx.event.ActionEvent; | |
| 37 | import javafx.scene.control.Button; | |
| 38 | import javafx.scene.control.Tooltip; | |
| 39 | import javafx.scene.input.KeyCode; | |
| 40 | import javafx.scene.input.KeyEvent; | |
| 41 | import javafx.stage.FileChooser; | |
| 42 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 43 | import com.scrivenvar.Messages; | |
| 44 | import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; | |
| 45 | import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory; | |
| 46 | ||
| 47 | /** | |
| 48 | * Button that opens a file chooser to select a local file for a URL in markdown. | |
| 49 | * | |
| 50 | * @author Karl Tauber | |
| 51 | */ | |
| 52 | public class BrowseFileButton | |
| 53 | extends Button | |
| 54 | { | |
| 55 | private final List<ExtensionFilter> extensionFilters = new ArrayList<>(); | |
| 56 | ||
| 57 | public BrowseFileButton() { | |
| 58 | setGraphic(FontAwesomeIconFactory.get().createIcon(FontAwesomeIcon.FILE_ALT, "1.2em")); | |
| 59 | setTooltip(new Tooltip(Messages.get("BrowseFileButton.tooltip"))); | |
| 60 | setOnAction(this::browse); | |
| 61 | ||
| 62 | disableProperty().bind(basePath.isNull()); | |
| 63 | ||
| 64 | // workaround for a JavaFX bug: | |
| 65 | // avoid closing the dialog that contains this control when the user | |
| 66 | // closes the FileChooser or DirectoryChooser using the ESC key | |
| 67 | addEventHandler(KeyEvent.KEY_RELEASED, e-> { | |
| 68 | if (e.getCode() == KeyCode.ESCAPE) | |
| 69 | e.consume(); | |
| 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 | public Path getBasePath() { return basePath.get(); } | |
| 80 | public void setBasePath(Path basePath) { this.basePath.set(basePath); } | |
| 81 | public ObjectProperty<Path> basePathProperty() { return basePath; } | |
| 82 | ||
| 83 | // 'url' property | |
| 84 | private final ObjectProperty<String> url = new SimpleObjectProperty<>(); | |
| 85 | public String getUrl() { return url.get(); } | |
| 86 | public void setUrl(String url) { this.url.set(url); } | |
| 87 | public ObjectProperty<String> urlProperty() { return url; } | |
| 88 | ||
| 89 | protected void browse(ActionEvent e) { | |
| 90 | FileChooser fileChooser = new FileChooser(); | |
| 91 | fileChooser.setTitle(Messages.get("BrowseFileButton.chooser.title")); | |
| 92 | fileChooser.getExtensionFilters().addAll(extensionFilters); | |
| 93 | fileChooser.getExtensionFilters().add(new ExtensionFilter(Messages.get("BrowseFileButton.chooser.allFilesFilter"), "*.*")); | |
| 94 | fileChooser.setInitialDirectory(getInitialDirectory()); | |
| 95 | File result = fileChooser.showOpenDialog(getScene().getWindow()); | |
| 96 | if (result != null) | |
| 97 | updateUrl(result); | |
| 98 | } | |
| 99 | ||
| 100 | protected File getInitialDirectory() { | |
| 101 | //TODO build initial directory based on current value of 'url' property | |
| 102 | return getBasePath().toFile(); | |
| 103 | } | |
| 104 | ||
| 105 | protected void updateUrl(File file) { | |
| 106 | String newUrl; | |
| 107 | try { | |
| 108 | newUrl = getBasePath().relativize(file.toPath()).toString(); | |
| 109 | } catch (IllegalArgumentException ex) { | |
| 110 | newUrl = file.toString(); | |
| 111 | } | |
| 112 | url.set(newUrl.replace('\\', '/')); | |
| 113 | } | |
| 114 | } | |
| 1 | 115 |
| 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 | 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 | import com.scrivenvar.util.Utils; | |
| 35 | ||
| 36 | /** | |
| 37 | * TextField that can escape/unescape characters for markdown. | |
| 38 | * | |
| 39 | * @author Karl Tauber | |
| 40 | */ | |
| 41 | public class EscapeTextField | |
| 42 | extends TextField | |
| 43 | { | |
| 44 | public EscapeTextField() { | |
| 45 | escapedText.bindBidirectional(textProperty(), new StringConverter<String>() { | |
| 46 | @Override public String toString(String object) { return escape(object); } | |
| 47 | @Override public String fromString(String string) { return unescape(string); } | |
| 48 | }); | |
| 49 | escapeCharacters.addListener(e -> escapedText.set(escape(textProperty().get()))); | |
| 50 | } | |
| 51 | ||
| 52 | // 'escapedText' property | |
| 53 | private final StringProperty escapedText = new SimpleStringProperty(); | |
| 54 | public String getEscapedText() { return escapedText.get(); } | |
| 55 | public void setEscapedText(String escapedText) { this.escapedText.set(escapedText); } | |
| 56 | public StringProperty escapedTextProperty() { return escapedText; } | |
| 57 | ||
| 58 | // 'escapeCharacters' property | |
| 59 | private final StringProperty escapeCharacters = new SimpleStringProperty(); | |
| 60 | public String getEscapeCharacters() { return escapeCharacters.get(); } | |
| 61 | public void setEscapeCharacters(String escapeCharacters) { this.escapeCharacters.set(escapeCharacters); } | |
| 62 | public StringProperty escapeCharactersProperty() { return escapeCharacters; } | |
| 63 | ||
| 64 | private String escape(String s) { | |
| 65 | String escapeChars = getEscapeCharacters(); | |
| 66 | return !Utils.isNullOrEmpty(escapeChars) | |
| 67 | ? s.replaceAll("([" + escapeChars.replaceAll("(.)", "\\\\$1") + "])", "\\\\$1") | |
| 68 | : s; | |
| 69 | } | |
| 70 | ||
| 71 | private String unescape(String s) { | |
| 72 | String escapeChars = getEscapeCharacters(); | |
| 73 | return !Utils.isNullOrEmpty(escapeChars) | |
| 74 | ? s.replaceAll("\\\\([" + escapeChars.replaceAll("(.)", "\\\\$1") + "])", "$1") | |
| 75 | : s; | |
| 76 | } | |
| 77 | } | |
| 1 | 78 |
| 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 | package com.scrivenvar.controls; | |
| 28 | ||
| 29 | import javafx.beans.property.IntegerProperty; | |
| 30 | import javafx.beans.property.SimpleIntegerProperty; | |
| 31 | import javafx.scene.control.CheckBox; | |
| 32 | ||
| 33 | /** | |
| 34 | * CheckBox that toggles a bit in an integer. | |
| 35 | * | |
| 36 | * @author Karl Tauber | |
| 37 | */ | |
| 38 | public class FlagCheckBox extends CheckBox { | |
| 39 | ||
| 40 | public FlagCheckBox() { | |
| 41 | setOnAction( e -> { | |
| 42 | if( isSelected() ) { | |
| 43 | setFlags( getFlags() | getFlag() ); | |
| 44 | } else { | |
| 45 | setFlags( getFlags() & ~getFlag() ); | |
| 46 | } | |
| 47 | } ); | |
| 48 | ||
| 49 | flags.addListener( (obs, oldFlags, newFlags) -> { | |
| 50 | setSelected( (newFlags.intValue() & getFlag()) != 0 ); | |
| 51 | } ); | |
| 52 | } | |
| 53 | ||
| 54 | // 'flag' property | |
| 55 | private final IntegerProperty flag = new SimpleIntegerProperty(); | |
| 56 | ||
| 57 | public int getFlag() { | |
| 58 | return flag.get(); | |
| 59 | } | |
| 60 | ||
| 61 | public void setFlag( int flag ) { | |
| 62 | this.flag.set( flag ); | |
| 63 | } | |
| 64 | ||
| 65 | public IntegerProperty flagProperty() { | |
| 66 | return flag; | |
| 67 | } | |
| 68 | ||
| 69 | // 'flags' property | |
| 70 | private final IntegerProperty flags = new SimpleIntegerProperty(); | |
| 71 | ||
| 72 | public int getFlags() { | |
| 73 | return flags.get(); | |
| 74 | } | |
| 75 | ||
| 76 | public void setFlags( int flags ) { | |
| 77 | this.flags.set( flags ); | |
| 78 | } | |
| 79 | ||
| 80 | public IntegerProperty flagsProperty() { | |
| 81 | return flags; | |
| 82 | } | |
| 83 | } | |
| 1 | 84 |
| 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 | package com.scrivenvar.controls; | |
| 29 | ||
| 30 | import javafx.beans.property.SimpleStringProperty; | |
| 31 | import javafx.beans.property.StringProperty; | |
| 32 | import javafx.scene.control.Hyperlink; | |
| 33 | import com.scrivenvar.Main; | |
| 34 | ||
| 35 | /** | |
| 36 | * Opens a web site in the default web browser. | |
| 37 | * | |
| 38 | * @author Karl Tauber | |
| 39 | */ | |
| 40 | public class WebHyperlink | |
| 41 | extends Hyperlink | |
| 42 | { | |
| 43 | public WebHyperlink() { | |
| 44 | setStyle("-fx-padding: 0; -fx-border-width: 0"); | |
| 45 | } | |
| 46 | ||
| 47 | @Override | |
| 48 | public void fire() { | |
| 49 | Main.showDocument(getUri()); | |
| 50 | } | |
| 51 | ||
| 52 | // 'uri' property | |
| 53 | private final StringProperty uri = new SimpleStringProperty(); | |
| 54 | public String getUri() { return uri.get(); } | |
| 55 | public void setUri(String uri) { this.uri.set(uri); } | |
| 56 | public StringProperty UriProperty() { return uri; } | |
| 57 | } | |
| 1 | 58 |
| 1 | /* | |
| 2 | * Copyright 2016 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.decorators; | |
| 29 | ||
| 30 | /** | |
| 31 | * Brackets variable names with <code>`r#</code> and <code>`</code>. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class RVariableDecorator implements VariableDecorator { | |
| 36 | ||
| 37 | /** | |
| 38 | * Returns the given string R-escaping backticks prepended and appended. This | |
| 39 | * is not null safe. Do not pass null into this method. | |
| 40 | * | |
| 41 | * @param variableName The string to decorate. | |
| 42 | * | |
| 43 | * @return "`r#" + variableName + "`". | |
| 44 | */ | |
| 45 | @Override | |
| 46 | public String decorate( final String variableName ) { | |
| 47 | return "`r#" + variableName + "`"; | |
| 48 | } | |
| 49 | } | |
| 1 | 50 |
| 1 | /* | |
| 2 | * Copyright 2016 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.decorators; | |
| 29 | ||
| 30 | /** | |
| 31 | * Responsible for updating variable names to use a machine-readable format | |
| 32 | * corresponding to the type of file being edited. | |
| 33 | */ | |
| 34 | public interface VariableDecorator { | |
| 35 | ||
| 36 | /** | |
| 37 | * This decorates a variable name based on some criteria determined by the | |
| 38 | * factory that creates implementations of this interface. | |
| 39 | * | |
| 40 | * @param variableName The text to decorate as per the filename extension | |
| 41 | * would indicate (e.g., ".md" goes to $VAR$ while ".Rmd" goes to `r#VAR`). | |
| 42 | * | |
| 43 | * @return The given variable name modified with its requisite delimiters. | |
| 44 | */ | |
| 45 | public String decorate( String variableName ); | |
| 46 | } | |
| 1 | 47 |
| 1 | /* | |
| 2 | * Copyright 2016 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.decorators; | |
| 29 | ||
| 30 | /** | |
| 31 | * Brackets variable names with dollar symbols. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class YamlVariableDecorator implements VariableDecorator { | |
| 36 | ||
| 37 | /** | |
| 38 | * Matches variables delimited by dollar symbols. The outer group is necessary | |
| 39 | * for substring replacement of delimited references. | |
| 40 | */ | |
| 41 | public final static String REGEX = "(\\$(.*?)\\$)"; | |
| 42 | ||
| 43 | /** | |
| 44 | * Returns the given string with a $ symbol prepended and appended. This is | |
| 45 | * not null safe. Do not pass null into this method. | |
| 46 | * | |
| 47 | * @param variableName The string to decorate. | |
| 48 | * | |
| 49 | * @return "$" + variableName + "$". | |
| 50 | */ | |
| 51 | @Override | |
| 52 | public String decorate( final String variableName ) { | |
| 53 | return "$" + variableName + "$"; | |
| 54 | } | |
| 55 | } | |
| 1 | 56 |
| 1 | /* | |
| 2 | * Copyright 2016 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 static com.scrivenvar.Constants.SEPARATOR; | |
| 31 | import static com.scrivenvar.definition.Lists.getFirst; | |
| 32 | import com.scrivenvar.predicates.strings.ContainsPredicate; | |
| 33 | import com.scrivenvar.predicates.strings.StartsPredicate; | |
| 34 | import com.scrivenvar.predicates.strings.StringPredicate; | |
| 35 | import com.scrivenvar.ui.AbstractPane; | |
| 36 | import com.scrivenvar.ui.VariableTreeItem; | |
| 37 | import java.util.List; | |
| 38 | import javafx.collections.ObservableList; | |
| 39 | import javafx.scene.Node; | |
| 40 | import javafx.scene.control.MultipleSelectionModel; | |
| 41 | import javafx.scene.control.SelectionMode; | |
| 42 | import javafx.scene.control.TreeItem; | |
| 43 | import javafx.scene.control.TreeView; | |
| 44 | ||
| 45 | /** | |
| 46 | * Provides a list of variables that can be referenced in the editor. | |
| 47 | * | |
| 48 | * @author White Magic Software, Ltd. | |
| 49 | */ | |
| 50 | public class DefinitionPane extends AbstractPane { | |
| 51 | ||
| 52 | private final static String TERMINALS = ":;,.!?-/\\¡¿"; | |
| 53 | ||
| 54 | private TreeView<String> treeView; | |
| 55 | ||
| 56 | /** | |
| 57 | * Constructs a definition pane with a given tree view root. | |
| 58 | * | |
| 59 | * @see YamlTreeAdapter.adapt | |
| 60 | * @param root The root of the variable definition tree. | |
| 61 | */ | |
| 62 | public DefinitionPane( final TreeView<String> root ) { | |
| 63 | setTreeView( root ); | |
| 64 | initTreeView(); | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Finds a tree item with a value that exactly matches the given word. | |
| 69 | * | |
| 70 | * @param trunk The root item containing a list of nodes to search. | |
| 71 | * @param word The value of the item to find. | |
| 72 | * @param predicate Helps determine whether the node value matches the word. | |
| 73 | * | |
| 74 | * @return The item that matches the given word, or null if not found. | |
| 75 | */ | |
| 76 | private TreeItem<String> findNode( | |
| 77 | final TreeItem<String> trunk, | |
| 78 | final StringPredicate predicate ) { | |
| 79 | final List<TreeItem<String>> branches = trunk.getChildren(); | |
| 80 | TreeItem<String> result = null; | |
| 81 | ||
| 82 | for( final TreeItem<String> leaf : branches ) { | |
| 83 | if( predicate.test( leaf.getValue() ) ) { | |
| 84 | result = leaf; | |
| 85 | break; | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | return result; | |
| 90 | } | |
| 91 | ||
| 92 | /** | |
| 93 | * Calls findNode with the EqualsPredicate. | |
| 94 | * | |
| 95 | * @see findNode( TreeItem, String, Predicate ) | |
| 96 | * @return The result from findNode. | |
| 97 | */ | |
| 98 | private TreeItem<String> findStartsNode( | |
| 99 | final TreeItem<String> trunk, | |
| 100 | final String word ) { | |
| 101 | return findNode( trunk, new StartsPredicate( word ) ); | |
| 102 | } | |
| 103 | ||
| 104 | /** | |
| 105 | * Calls findNode with the ContainsPredicate. | |
| 106 | * | |
| 107 | * @see findNode( TreeItem, String, Predicate ) | |
| 108 | * @return The result from findNode. | |
| 109 | */ | |
| 110 | private TreeItem<String> findSubstringNode( | |
| 111 | final TreeItem<String> trunk, | |
| 112 | final String word ) { | |
| 113 | return findNode( trunk, new ContainsPredicate( word ) ); | |
| 114 | } | |
| 115 | ||
| 116 | /** | |
| 117 | * Finds a node that matches a prefix and suffix specified by the given path | |
| 118 | * variable. The prefix must match a valid node value. The suffix refers to | |
| 119 | * the start of a string that matches zero or more children of the node | |
| 120 | * specified by the prefix. The algorithm has the following cases: | |
| 121 | * | |
| 122 | * <ol> | |
| 123 | * <li>Path is empty, return first child.</li> | |
| 124 | * <li>Path contains a complete match, return corresponding node.</li> | |
| 125 | * <li>Path contains a partial match, return nearest node.</li> | |
| 126 | * <li>Path contains a complete and partial match, return nearest node.</li> | |
| 127 | * </ol> | |
| 128 | * | |
| 129 | * @param path The word typed by the user, which contains dot-separated node | |
| 130 | * names that represent a path within the YAML tree plus a partial variable | |
| 131 | * name match (for a node). | |
| 132 | * | |
| 133 | * @return The node value that starts with the suffix portion of the given | |
| 134 | * path, never null. | |
| 135 | */ | |
| 136 | public TreeItem<String> findNode( String path ) { | |
| 137 | TreeItem<String> cItem = getTreeRoot(); | |
| 138 | TreeItem<String> pItem = cItem; | |
| 139 | ||
| 140 | int index = path.indexOf( SEPARATOR ); | |
| 141 | ||
| 142 | while( index >= 0 ) { | |
| 143 | final String node = path.substring( 0, index ); | |
| 144 | path = path.substring( index + 1 ); | |
| 145 | ||
| 146 | if( (cItem = findStartsNode( cItem, node )) == null ) { | |
| 147 | break; | |
| 148 | } | |
| 149 | ||
| 150 | index = path.indexOf( SEPARATOR ); | |
| 151 | pItem = cItem; | |
| 152 | } | |
| 153 | ||
| 154 | // Find the node that starts with whatever the user typed. | |
| 155 | cItem = findStartsNode( pItem, path ); | |
| 156 | ||
| 157 | // If there was no matching node, then find a substring match. | |
| 158 | if( cItem == null ) { | |
| 159 | cItem = findSubstringNode( pItem, path ); | |
| 160 | } | |
| 161 | ||
| 162 | // If neither starts with nor substring matched a node, revert to the last | |
| 163 | // known valid node. | |
| 164 | if( cItem == null ) { | |
| 165 | cItem = pItem; | |
| 166 | } | |
| 167 | ||
| 168 | return sanitize( cItem ); | |
| 169 | } | |
| 170 | ||
| 171 | /** | |
| 172 | * Returns the leaf that matches the given value. If the value is terminally | |
| 173 | * punctuated, the punctuation is removed if no match was found. | |
| 174 | * | |
| 175 | * @param value The value to find, never null. | |
| 176 | * | |
| 177 | * @return The leaf that contains the given value, or null if neither the | |
| 178 | * original value nor the terminally-trimmed value was found. | |
| 179 | */ | |
| 180 | public VariableTreeItem<String> findLeaf( final String value ) { | |
| 181 | final VariableTreeItem<String> root = getTreeRoot(); | |
| 182 | final VariableTreeItem<String> leaf = root.findLeaf( value ); | |
| 183 | ||
| 184 | return leaf == null | |
| 185 | ? root.findLeaf( rtrimTerminalPunctuation( value ) ) | |
| 186 | : leaf; | |
| 187 | } | |
| 188 | ||
| 189 | /** | |
| 190 | * Removes punctuation from the end of a string. The character set includes: | |
| 191 | * <code>:;,.!?-/\¡¿</code>. | |
| 192 | * | |
| 193 | * @param s The string to trim, never null. | |
| 194 | * | |
| 195 | * @return The string trimmed of all terminal characters from the end | |
| 196 | */ | |
| 197 | private String rtrimTerminalPunctuation( final String s ) { | |
| 198 | final StringBuilder result = new StringBuilder( s.trim() ); | |
| 199 | ||
| 200 | while( TERMINALS.contains( "" + result.charAt( result.length() - 1 ) ) ) { | |
| 201 | result.setLength( result.length() - 1 ); | |
| 202 | } | |
| 203 | ||
| 204 | return result.toString(); | |
| 205 | } | |
| 206 | ||
| 207 | /** | |
| 208 | * Returns the tree root if either item or its first child are null. | |
| 209 | * | |
| 210 | * @param item The item to make null safe. | |
| 211 | * | |
| 212 | * @return A non-null TreeItem, possibly the root item (to avoid null). | |
| 213 | */ | |
| 214 | private TreeItem<String> sanitize( final TreeItem<String> item ) { | |
| 215 | final TreeItem<String> result = item == getTreeRoot() | |
| 216 | ? getFirst( item.getChildren() ) | |
| 217 | : item; | |
| 218 | ||
| 219 | return result == null ? item : result; | |
| 220 | } | |
| 221 | ||
| 222 | /** | |
| 223 | * Expands the node to the root, recursively. | |
| 224 | * | |
| 225 | * @param <T> The type of tree item to expand (usually String). | |
| 226 | * @param node The node to expand. | |
| 227 | */ | |
| 228 | public <T> void expand( final TreeItem<T> node ) { | |
| 229 | if( node != null ) { | |
| 230 | expand( node.getParent() ); | |
| 231 | ||
| 232 | if( !node.isLeaf() ) { | |
| 233 | node.setExpanded( true ); | |
| 234 | } | |
| 235 | } | |
| 236 | } | |
| 237 | ||
| 238 | public void select( final TreeItem<String> item ) { | |
| 239 | clearSelection(); | |
| 240 | selectItem( getTreeView().getRow( item ) ); | |
| 241 | } | |
| 242 | ||
| 243 | private void clearSelection() { | |
| 244 | getSelectionModel().clearSelection(); | |
| 245 | } | |
| 246 | ||
| 247 | private void selectItem( final int row ) { | |
| 248 | getSelectionModel().select( row ); | |
| 249 | } | |
| 250 | ||
| 251 | /** | |
| 252 | * Collapses the tree, recursively. | |
| 253 | */ | |
| 254 | public void collapse() { | |
| 255 | collapse( getTreeRoot().getChildren() ); | |
| 256 | } | |
| 257 | ||
| 258 | /** | |
| 259 | * Collapses the tree, recursively. | |
| 260 | * | |
| 261 | * @param <T> The type of tree item to expand (usually String). | |
| 262 | * @param node The nodes to collapse. | |
| 263 | */ | |
| 264 | private <T> void collapse( ObservableList<TreeItem<T>> nodes ) { | |
| 265 | for( final TreeItem<T> node : nodes ) { | |
| 266 | node.setExpanded( false ); | |
| 267 | collapse( node.getChildren() ); | |
| 268 | } | |
| 269 | } | |
| 270 | ||
| 271 | private void initTreeView() { | |
| 272 | getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE ); | |
| 273 | } | |
| 274 | ||
| 275 | /** | |
| 276 | * Returns the root node to the tree view. | |
| 277 | * | |
| 278 | * @return getTreeView() | |
| 279 | */ | |
| 280 | public Node getNode() { | |
| 281 | return getTreeView(); | |
| 282 | } | |
| 283 | ||
| 284 | private MultipleSelectionModel getSelectionModel() { | |
| 285 | return getTreeView().getSelectionModel(); | |
| 286 | } | |
| 287 | ||
| 288 | /** | |
| 289 | * Returns the tree view that contains the YAML definition hierarchy. | |
| 290 | * | |
| 291 | * @return A non-null instance. | |
| 292 | */ | |
| 293 | private TreeView<String> getTreeView() { | |
| 294 | return this.treeView; | |
| 295 | } | |
| 296 | ||
| 297 | /** | |
| 298 | * Returns the root of the tree. | |
| 299 | * | |
| 300 | * @return The first node added to the YAML definition tree. | |
| 301 | */ | |
| 302 | private VariableTreeItem<String> getTreeRoot() { | |
| 303 | return (VariableTreeItem<String>)getTreeView().getRoot(); | |
| 304 | } | |
| 305 | ||
| 306 | public <T> boolean isRoot( final TreeItem<T> item ) { | |
| 307 | return getTreeRoot().equals( item ); | |
| 308 | } | |
| 309 | ||
| 310 | /** | |
| 311 | * Sets the tree view (called by the constructor). | |
| 312 | * | |
| 313 | * @param treeView | |
| 314 | */ | |
| 315 | private void setTreeView( TreeView<String> treeView ) { | |
| 316 | if( treeView != null ) { | |
| 317 | this.treeView = treeView; | |
| 318 | } | |
| 319 | } | |
| 320 | } | |
| 1 | 321 |
| 1 | /* | |
| 2 | * Copyright 2016 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 java.util.List; | |
| 31 | ||
| 32 | /** | |
| 33 | * Convenience class that provides a clearer API for obtaining list elements. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public final class Lists { | |
| 38 | ||
| 39 | private Lists() { | |
| 40 | } | |
| 41 | ||
| 42 | /** | |
| 43 | * Returns the first item in the given list, or null if not found. | |
| 44 | * | |
| 45 | * @param <T> The generic list type. | |
| 46 | * @param list The list that may have a first item. | |
| 47 | * | |
| 48 | * @return null if the list is null or there is no first item. | |
| 49 | */ | |
| 50 | public static <T> T getFirst( final List<T> list ) { | |
| 51 | return getFirst( list, null ); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Returns the last item in the given list, or null if not found. | |
| 56 | * | |
| 57 | * @param <T> The generic list type. | |
| 58 | * @param list The list that may have a last item. | |
| 59 | * | |
| 60 | * @return null if the list is null or there is no last item. | |
| 61 | */ | |
| 62 | public static <T> T getLast( final List<T> list ) { | |
| 63 | return getLast( list, null ); | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Returns the first item in the given list, or t if not found. | |
| 68 | * | |
| 69 | * @param <T> The generic list type. | |
| 70 | * @param list The list that may have a first item. | |
| 71 | * @param t The default return value. | |
| 72 | * | |
| 73 | * @return null if the list is null or there is no first item. | |
| 74 | */ | |
| 75 | public static <T> T getFirst( final List<T> list, final T t ) { | |
| 76 | return isEmpty( list ) ? t : list.get( 0 ); | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Returns the last item in the given list, or t if not found. | |
| 81 | * | |
| 82 | * @param <T> The generic list type. | |
| 83 | * @param list The list that may have a last item. | |
| 84 | * @param t The default return value. | |
| 85 | * | |
| 86 | * @return null if the list is null or there is no last item. | |
| 87 | */ | |
| 88 | public static <T> T getLast( final List<T> list, final T t ) { | |
| 89 | return isEmpty( list ) ? t : list.get( list.size() - 1 ); | |
| 90 | } | |
| 91 | ||
| 92 | /** | |
| 93 | * Returns true if the given list is null or empty. | |
| 94 | * | |
| 95 | * @param <T> The generic list type. | |
| 96 | * @param list The list that has a last item. | |
| 97 | * | |
| 98 | * @return true The list is empty. | |
| 99 | */ | |
| 100 | public static <T> boolean isEmpty( final List<T> list ) { | |
| 101 | return list == null || list.isEmpty(); | |
| 102 | } | |
| 103 | } | |
| 1 | 104 |
| 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 | package com.scrivenvar.dialogs; | |
| 28 | ||
| 29 | import com.scrivenvar.Messages; | |
| 30 | import com.scrivenvar.controls.BrowseFileButton; | |
| 31 | import com.scrivenvar.controls.EscapeTextField; | |
| 32 | import com.scrivenvar.service.events.impl.ButtonOrderPane; | |
| 33 | import java.nio.file.Path; | |
| 34 | import javafx.application.Platform; | |
| 35 | import javafx.beans.binding.Bindings; | |
| 36 | import javafx.beans.property.SimpleStringProperty; | |
| 37 | import javafx.beans.property.StringProperty; | |
| 38 | import javafx.scene.control.ButtonBar.ButtonData; | |
| 39 | import javafx.scene.control.ButtonType; | |
| 40 | import javafx.scene.control.Dialog; | |
| 41 | import javafx.scene.control.DialogPane; | |
| 42 | import javafx.scene.control.Label; | |
| 43 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 44 | import javafx.stage.Window; | |
| 45 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 46 | ||
| 47 | /** | |
| 48 | * Dialog to enter a markdown image. | |
| 49 | * | |
| 50 | * @author Karl Tauber | |
| 51 | */ | |
| 52 | public class ImageDialog extends Dialog<String> { | |
| 53 | ||
| 54 | private final StringProperty image = new SimpleStringProperty(); | |
| 55 | ||
| 56 | public ImageDialog( Window owner, Path basePath ) { | |
| 57 | setTitle( Messages.get( "ImageDialog.title" ) ); | |
| 58 | initOwner( owner ); | |
| 59 | setResizable( true ); | |
| 60 | ||
| 61 | initComponents(); | |
| 62 | ||
| 63 | linkBrowseFileButton.setBasePath( basePath ); | |
| 64 | linkBrowseFileButton.addExtensionFilter( new ExtensionFilter( Messages.get( "ImageDialog.chooser.imagesFilter" ), "*.png", "*.gif", "*.jpg" ) ); | |
| 65 | linkBrowseFileButton.urlProperty().bindBidirectional( urlField.escapedTextProperty() ); | |
| 66 | ||
| 67 | setDialogPane( new ButtonOrderPane() ); | |
| 68 | final DialogPane dialogPane = getDialogPane(); | |
| 69 | dialogPane.setContent( pane ); | |
| 70 | dialogPane.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL ); | |
| 71 | ||
| 72 | dialogPane.lookupButton( ButtonType.OK ).disableProperty().bind( | |
| 73 | urlField.escapedTextProperty().isEmpty() | |
| 74 | .or( textField.escapedTextProperty().isEmpty() ) ); | |
| 75 | ||
| 76 | image.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | |
| 77 | .then( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | |
| 78 | .otherwise( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) ); | |
| 79 | previewField.textProperty().bind( image ); | |
| 80 | ||
| 81 | setResultConverter( dialogButton -> { | |
| 82 | ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | |
| 83 | return (data == ButtonData.OK_DONE) ? image.get() : null; | |
| 84 | } ); | |
| 85 | ||
| 86 | Platform.runLater( () -> { | |
| 87 | urlField.requestFocus(); | |
| 88 | ||
| 89 | if( urlField.getText().startsWith( "http://" ) ) { | |
| 90 | urlField.selectRange( "http://".length(), urlField.getLength() ); | |
| 91 | } | |
| 92 | } ); | |
| 93 | } | |
| 94 | ||
| 95 | private void initComponents() { | |
| 96 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 97 | pane = new MigPane(); | |
| 98 | Label urlLabel = new Label(); | |
| 99 | urlField = new EscapeTextField(); | |
| 100 | linkBrowseFileButton = new BrowseFileButton(); | |
| 101 | Label textLabel = new Label(); | |
| 102 | textField = new EscapeTextField(); | |
| 103 | Label titleLabel = new Label(); | |
| 104 | titleField = new EscapeTextField(); | |
| 105 | Label previewLabel = new Label(); | |
| 106 | previewField = new Label(); | |
| 107 | ||
| 108 | //======== pane ======== | |
| 109 | { | |
| 110 | pane.setCols( "[shrink 0,fill][300,grow,fill][fill]" ); | |
| 111 | pane.setRows( "[][][][]" ); | |
| 112 | ||
| 113 | //---- urlLabel ---- | |
| 114 | urlLabel.setText( Messages.get( "ImageDialog.urlLabel.text" ) ); | |
| 115 | pane.add( urlLabel, "cell 0 0" ); | |
| 116 | ||
| 117 | //---- urlField ---- | |
| 118 | urlField.setEscapeCharacters( "()" ); | |
| 119 | urlField.setText( "http://yourlink.com" ); | |
| 120 | urlField.setPromptText( "http://yourlink.com" ); | |
| 121 | pane.add( urlField, "cell 1 0" ); | |
| 122 | pane.add( linkBrowseFileButton, "cell 2 0" ); | |
| 123 | ||
| 124 | //---- textLabel ---- | |
| 125 | textLabel.setText( Messages.get( "ImageDialog.textLabel.text" ) ); | |
| 126 | pane.add( textLabel, "cell 0 1" ); | |
| 127 | ||
| 128 | //---- textField ---- | |
| 129 | textField.setEscapeCharacters( "[]" ); | |
| 130 | pane.add( textField, "cell 1 1 2 1" ); | |
| 131 | ||
| 132 | //---- titleLabel ---- | |
| 133 | titleLabel.setText( Messages.get( "ImageDialog.titleLabel.text" ) ); | |
| 134 | pane.add( titleLabel, "cell 0 2" ); | |
| 135 | pane.add( titleField, "cell 1 2 2 1" ); | |
| 136 | ||
| 137 | //---- previewLabel ---- | |
| 138 | previewLabel.setText( Messages.get( "ImageDialog.previewLabel.text" ) ); | |
| 139 | pane.add( previewLabel, "cell 0 3" ); | |
| 140 | pane.add( previewField, "cell 1 3 2 1" ); | |
| 141 | } | |
| 142 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 143 | } | |
| 144 | ||
| 145 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 146 | private MigPane pane; | |
| 147 | private EscapeTextField urlField; | |
| 148 | private BrowseFileButton linkBrowseFileButton; | |
| 149 | private EscapeTextField textField; | |
| 150 | private EscapeTextField titleField; | |
| 151 | private Label previewField; | |
| 152 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 153 | } | |
| 1 | 154 |
| 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.Messages; | |
| 31 | import com.scrivenvar.controls.EscapeTextField; | |
| 32 | import com.scrivenvar.editor.HyperlinkModel; | |
| 33 | import com.scrivenvar.service.events.impl.ButtonOrderPane; | |
| 34 | import java.nio.file.Path; | |
| 35 | import javafx.application.Platform; | |
| 36 | import javafx.beans.binding.Bindings; | |
| 37 | import javafx.beans.property.SimpleStringProperty; | |
| 38 | import javafx.beans.property.StringProperty; | |
| 39 | import javafx.scene.control.ButtonBar.ButtonData; | |
| 40 | import javafx.scene.control.ButtonType; | |
| 41 | import javafx.scene.control.Dialog; | |
| 42 | import javafx.scene.control.DialogPane; | |
| 43 | import javafx.scene.control.Label; | |
| 44 | import javafx.stage.Window; | |
| 45 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 46 | ||
| 47 | /** | |
| 48 | * Dialog to enter a markdown link. | |
| 49 | * | |
| 50 | * @author Karl Tauber | |
| 51 | */ | |
| 52 | public class LinkDialog extends Dialog<String> { | |
| 53 | ||
| 54 | private final StringProperty link = new SimpleStringProperty(); | |
| 55 | ||
| 56 | public LinkDialog( final Window owner, final HyperlinkModel hyperlink, final Path basePath ) { | |
| 57 | setTitle( Messages.get( "LinkDialog.title" ) ); | |
| 58 | initOwner( owner ); | |
| 59 | setResizable( true ); | |
| 60 | ||
| 61 | initComponents(); | |
| 62 | ||
| 63 | setDialogPane( new ButtonOrderPane() ); | |
| 64 | ||
| 65 | final DialogPane dialog = getDialogPane(); | |
| 66 | dialog.setContent( pane ); | |
| 67 | dialog.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL ); | |
| 68 | ||
| 69 | dialog.lookupButton( ButtonType.OK ).disableProperty().bind( | |
| 70 | urlField.escapedTextProperty().isEmpty() ); | |
| 71 | ||
| 72 | textField.setText( hyperlink.getText() ); | |
| 73 | urlField.setText( hyperlink.getUrl() ); | |
| 74 | titleField.setText( hyperlink.getTitle() ); | |
| 75 | ||
| 76 | link.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | |
| 77 | .then( Bindings.format( "[%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | |
| 78 | .otherwise( Bindings.when( textField.escapedTextProperty().isNotEmpty() ) | |
| 79 | .then( Bindings.format( "[%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) | |
| 80 | .otherwise( urlField.escapedTextProperty() ) ) ); | |
| 81 | ||
| 82 | setResultConverter( dialogButton -> { | |
| 83 | ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | |
| 84 | return (data == ButtonData.OK_DONE) ? link.get() : null; | |
| 85 | } ); | |
| 86 | ||
| 87 | Platform.runLater( () -> { | |
| 88 | urlField.requestFocus(); | |
| 89 | urlField.selectRange( 0, urlField.getLength() ); | |
| 90 | } ); | |
| 91 | } | |
| 92 | ||
| 93 | private void initComponents() { | |
| 94 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 95 | pane = new MigPane(); | |
| 96 | Label urlLabel = new Label(); | |
| 97 | urlField = new EscapeTextField(); | |
| 98 | Label textLabel = new Label(); | |
| 99 | textField = new EscapeTextField(); | |
| 100 | Label titleLabel = new Label(); | |
| 101 | titleField = new EscapeTextField(); | |
| 102 | ||
| 103 | //======== pane ======== | |
| 104 | { | |
| 105 | pane.setCols( "[shrink 0,fill][300,grow,fill][fill][fill]" ); | |
| 106 | pane.setRows( "[][][][]" ); | |
| 107 | ||
| 108 | //---- urlLabel ---- | |
| 109 | urlLabel.setText( Messages.get( "LinkDialog.urlLabel.text" ) ); | |
| 110 | pane.add( urlLabel, "cell 0 0" ); | |
| 111 | ||
| 112 | //---- urlField ---- | |
| 113 | urlField.setEscapeCharacters( "()" ); | |
| 114 | pane.add( urlField, "cell 1 0" ); | |
| 115 | ||
| 116 | //---- textLabel ---- | |
| 117 | textLabel.setText( Messages.get( "LinkDialog.textLabel.text" ) ); | |
| 118 | pane.add( textLabel, "cell 0 1" ); | |
| 119 | ||
| 120 | //---- textField ---- | |
| 121 | textField.setEscapeCharacters( "[]" ); | |
| 122 | pane.add( textField, "cell 1 1 3 1" ); | |
| 123 | ||
| 124 | //---- titleLabel ---- | |
| 125 | titleLabel.setText( Messages.get( "LinkDialog.titleLabel.text" ) ); | |
| 126 | pane.add( titleLabel, "cell 0 2" ); | |
| 127 | pane.add( titleField, "cell 1 2 3 1" ); | |
| 128 | } | |
| 129 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 130 | } | |
| 131 | ||
| 132 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 133 | private MigPane pane; | |
| 134 | private EscapeTextField urlField; | |
| 135 | private EscapeTextField textField; | |
| 136 | private EscapeTextField titleField; | |
| 137 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 138 | } | |
| 1 | 139 |
| 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 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.editor; | |
| 29 | ||
| 30 | import com.scrivenvar.ui.AbstractPane; | |
| 31 | import java.nio.file.Path; | |
| 32 | import java.util.function.Consumer; | |
| 33 | import javafx.application.Platform; | |
| 34 | import javafx.beans.property.ObjectProperty; | |
| 35 | import javafx.beans.property.ReadOnlyDoubleProperty; | |
| 36 | import javafx.beans.property.ReadOnlyDoubleWrapper; | |
| 37 | import javafx.beans.property.SimpleObjectProperty; | |
| 38 | import javafx.beans.value.ChangeListener; | |
| 39 | import javafx.event.Event; | |
| 40 | import javafx.scene.control.ScrollPane; | |
| 41 | import javafx.scene.input.InputEvent; | |
| 42 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 43 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 44 | import org.fxmisc.undo.UndoManager; | |
| 45 | import org.fxmisc.wellbehaved.event.EventPattern; | |
| 46 | import org.fxmisc.wellbehaved.event.InputMap; | |
| 47 | import static org.fxmisc.wellbehaved.event.InputMap.consume; | |
| 48 | import org.fxmisc.wellbehaved.event.Nodes; | |
| 49 | ||
| 50 | /** | |
| 51 | * Represents common editing features for various types of text editors. | |
| 52 | * | |
| 53 | * @author White Magic Software, Ltd. | |
| 54 | */ | |
| 55 | public class EditorPane extends AbstractPane { | |
| 56 | ||
| 57 | private StyleClassedTextArea editor; | |
| 58 | private VirtualizedScrollPane<StyleClassedTextArea> scrollPane; | |
| 59 | private final ReadOnlyDoubleWrapper scrollY = new ReadOnlyDoubleWrapper(); | |
| 60 | private final ObjectProperty<Path> path = new SimpleObjectProperty<>(); | |
| 61 | ||
| 62 | private String lineSeparator = getLineSeparator(); | |
| 63 | ||
| 64 | /** | |
| 65 | * Set when entering variable edit mode; retrieved upon exiting. | |
| 66 | */ | |
| 67 | private InputMap<InputEvent> nodeMap; | |
| 68 | ||
| 69 | @Override | |
| 70 | public void requestFocus() { | |
| 71 | Platform.runLater( () -> getEditor().requestFocus() ); | |
| 72 | } | |
| 73 | ||
| 74 | public void undo() { | |
| 75 | getUndoManager().undo(); | |
| 76 | } | |
| 77 | ||
| 78 | public void redo() { | |
| 79 | getUndoManager().redo(); | |
| 80 | } | |
| 81 | ||
| 82 | public UndoManager getUndoManager() { | |
| 83 | return getEditor().getUndoManager(); | |
| 84 | } | |
| 85 | ||
| 86 | public String getText() { | |
| 87 | String text = getEditor().getText(); | |
| 88 | ||
| 89 | if( !this.lineSeparator.equals( "\n" ) ) { | |
| 90 | text = text.replace( "\n", this.lineSeparator ); | |
| 91 | } | |
| 92 | ||
| 93 | return text; | |
| 94 | } | |
| 95 | ||
| 96 | public void setText( final String text ) { | |
| 97 | this.lineSeparator = determineLineSeparator( text ); | |
| 98 | getEditor().deselect(); | |
| 99 | getEditor().replaceText( text ); | |
| 100 | getUndoManager().mark(); | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Call to hook into changes to the text area. | |
| 105 | * | |
| 106 | * @param listener Receives editor text change events. | |
| 107 | */ | |
| 108 | public void addChangeListener( final ChangeListener<? super String> listener ) { | |
| 109 | getEditor().textProperty().addListener( listener ); | |
| 110 | } | |
| 111 | ||
| 112 | /** | |
| 113 | * Call to listen for when the caret moves to another paragraph. | |
| 114 | * | |
| 115 | * @param listener Receives paragraph change events. | |
| 116 | */ | |
| 117 | public void addCaretParagraphListener( final ChangeListener<? super Integer> listener ) { | |
| 118 | getEditor().currentParagraphProperty().addListener( listener ); | |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * This method adds listeners to editor events. | |
| 123 | * | |
| 124 | * @param <T> The event type. | |
| 125 | * @param <U> The consumer type for the given event type. | |
| 126 | * @param event The event of interest. | |
| 127 | * @param consumer The method to call when the event happens. | |
| 128 | */ | |
| 129 | public <T extends Event, U extends T> void addEventListener( | |
| 130 | final EventPattern<? super T, ? extends U> event, | |
| 131 | final Consumer<? super U> consumer ) { | |
| 132 | Nodes.addInputMap( getEditor(), consume( event, consumer ) ); | |
| 133 | } | |
| 134 | ||
| 135 | /** | |
| 136 | * This method adds listeners to editor events that can be removed without | |
| 137 | * affecting the original listeners (i.e., the original lister is restored on | |
| 138 | * a call to removeEventListener). | |
| 139 | * | |
| 140 | * @param map The map of methods to events. | |
| 141 | */ | |
| 142 | @SuppressWarnings( "unchecked" ) | |
| 143 | public void addEventListener( final InputMap<InputEvent> map ) { | |
| 144 | this.nodeMap = (InputMap<InputEvent>)getInputMap(); | |
| 145 | Nodes.addInputMap( getEditor(), map ); | |
| 146 | } | |
| 147 | ||
| 148 | /** | |
| 149 | * This method removes listeners to editor events and restores the default | |
| 150 | * handler. | |
| 151 | * | |
| 152 | * @param map The map of methods to events. | |
| 153 | */ | |
| 154 | public void removeEventListener( final InputMap<InputEvent> map ) { | |
| 155 | Nodes.removeInputMap( getEditor(), map ); | |
| 156 | Nodes.addInputMap( getEditor(), this.nodeMap ); | |
| 157 | } | |
| 158 | ||
| 159 | /** | |
| 160 | * Returns the value for "org.fxmisc.wellbehaved.event.inputmap". | |
| 161 | * | |
| 162 | * @return An input map of input events. | |
| 163 | */ | |
| 164 | private Object getInputMap() { | |
| 165 | return getEditor().getProperties().get( getInputMapKey() ); | |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * Returns the hashmap key entry for the input map. | |
| 170 | * | |
| 171 | * @return "org.fxmisc.wellbehaved.event.inputmap" | |
| 172 | */ | |
| 173 | private String getInputMapKey() { | |
| 174 | return "org.fxmisc.wellbehaved.event.inputmap"; | |
| 175 | } | |
| 176 | ||
| 177 | public void scrollToTop() { | |
| 178 | getEditor().moveTo( 0 ); | |
| 179 | } | |
| 180 | ||
| 181 | private void setEditor( StyleClassedTextArea textArea ) { | |
| 182 | this.editor = textArea; | |
| 183 | } | |
| 184 | ||
| 185 | public synchronized StyleClassedTextArea getEditor() { | |
| 186 | if( this.editor == null ) { | |
| 187 | setEditor( createTextArea() ); | |
| 188 | } | |
| 189 | ||
| 190 | return this.editor; | |
| 191 | } | |
| 192 | ||
| 193 | /** | |
| 194 | * Returns the scroll pane that contains the text area. | |
| 195 | * | |
| 196 | * @return The scroll pane that contains the content to edit. | |
| 197 | */ | |
| 198 | public synchronized VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() { | |
| 199 | if( this.scrollPane == null ) { | |
| 200 | this.scrollPane = createScrollPane(); | |
| 201 | } | |
| 202 | ||
| 203 | return this.scrollPane; | |
| 204 | } | |
| 205 | ||
| 206 | protected VirtualizedScrollPane<StyleClassedTextArea> createScrollPane() { | |
| 207 | final VirtualizedScrollPane<StyleClassedTextArea> pane = new VirtualizedScrollPane<>( getEditor() ); | |
| 208 | pane.setVbarPolicy( ScrollPane.ScrollBarPolicy.ALWAYS ); | |
| 209 | ||
| 210 | return pane; | |
| 211 | } | |
| 212 | ||
| 213 | protected StyleClassedTextArea createTextArea() { | |
| 214 | return new StyleClassedTextArea( false ); | |
| 215 | } | |
| 216 | ||
| 217 | public double getScrollY() { | |
| 218 | return this.scrollY.get(); | |
| 219 | } | |
| 220 | ||
| 221 | protected void setScrollY( double scrolled ) { | |
| 222 | this.scrollY.set( scrolled ); | |
| 223 | } | |
| 224 | ||
| 225 | public ReadOnlyDoubleProperty scrollYProperty() { | |
| 226 | return this.scrollY.getReadOnlyProperty(); | |
| 227 | } | |
| 228 | ||
| 229 | public Path getPath() { | |
| 230 | return this.path.get(); | |
| 231 | } | |
| 232 | ||
| 233 | public void setPath( final Path path ) { | |
| 234 | this.path.set( path ); | |
| 235 | } | |
| 236 | ||
| 237 | public ObjectProperty<Path> pathProperty() { | |
| 238 | return this.path; | |
| 239 | } | |
| 240 | ||
| 241 | private String getLineSeparator() { | |
| 242 | final String separator = getOptions().getLineSeparator(); | |
| 243 | ||
| 244 | return (separator != null) | |
| 245 | ? separator | |
| 246 | : System.lineSeparator(); | |
| 247 | } | |
| 248 | ||
| 249 | private String determineLineSeparator( final String s ) { | |
| 250 | final int length = s.length(); | |
| 251 | ||
| 252 | // TODO: Looping backwards will probably detect a newline sooner. | |
| 253 | for( int i = 0; i < length; i++ ) { | |
| 254 | char ch = s.charAt( i ); | |
| 255 | if( ch == '\n' ) { | |
| 256 | return (i > 0 && s.charAt( i - 1 ) == '\r') ? "\r\n" : "\n"; | |
| 257 | } | |
| 258 | } | |
| 259 | ||
| 260 | return getLineSeparator(); | |
| 261 | } | |
| 262 | } | |
| 1 | 263 |
| 1 | /* | |
| 2 | * Copyright 2016 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.editor; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.ast.Link; | |
| 31 | ||
| 32 | /** | |
| 33 | * Represents the model for a hyperlink: text and url text. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public class HyperlinkModel { | |
| 38 | ||
| 39 | private String text; | |
| 40 | private String url; | |
| 41 | private String title; | |
| 42 | ||
| 43 | /** | |
| 44 | * Constructs a new hyperlink model in Markdown format by default with no | |
| 45 | * title (i.e., tooltip). | |
| 46 | * | |
| 47 | * @param text The hyperlink text displayed (e.g., displayed to the user). | |
| 48 | * @param url The destination URL (e.g., when clicked). | |
| 49 | */ | |
| 50 | public HyperlinkModel( final String text, final String url ) { | |
| 51 | this( text, url, null ); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Constructs a new hyperlink model for the given AST link. | |
| 56 | * | |
| 57 | * @param link A markdown link. | |
| 58 | */ | |
| 59 | public HyperlinkModel( final Link link ) { | |
| 60 | this( | |
| 61 | link.getText().toString(), | |
| 62 | link.getUrl().toString(), | |
| 63 | link.getTitle().toString() | |
| 64 | ); | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Constructs a new hyperlink model in Markdown format by default. | |
| 69 | * | |
| 70 | * @param text The hyperlink text displayed (e.g., displayed to the user). | |
| 71 | * @param url The destination URL (e.g., when clicked). | |
| 72 | * @param title The hyperlink title (e.g., shown as a tooltip). | |
| 73 | */ | |
| 74 | public HyperlinkModel( final String text, final String url, final String title ) { | |
| 75 | setText( text ); | |
| 76 | setUrl( url ); | |
| 77 | setTitle( title ); | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Returns the string in Markdown format by default. | |
| 82 | * | |
| 83 | * @return A markdown version of the hyperlink. | |
| 84 | */ | |
| 85 | @Override | |
| 86 | public String toString() { | |
| 87 | String format = "%s%s%s"; | |
| 88 | ||
| 89 | if( hasText() ) { | |
| 90 | format = "[%s]" + (hasTitle() ? "(%s \"%s\")" : "(%s%s)"); | |
| 91 | } | |
| 92 | ||
| 93 | // Becomes ""+URL+"" if no text is set. | |
| 94 | // Becomes [TITLE]+(URL)+"" if no title is set. | |
| 95 | // Becomes [TITLE]+(URL+ \"TITLE\") if title is set. | |
| 96 | return String.format( format, getText(), getUrl(), getTitle() ); | |
| 97 | } | |
| 98 | ||
| 99 | public final void setText( final String text ) { | |
| 100 | this.text = nullSafe( text ); | |
| 101 | } | |
| 102 | ||
| 103 | public final void setUrl( final String url ) { | |
| 104 | this.url = nullSafe( url ); | |
| 105 | } | |
| 106 | ||
| 107 | public final void setTitle( final String title ) { | |
| 108 | this.title = nullSafe( title ); | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Answers whether text has been set for the hyperlink. | |
| 113 | * | |
| 114 | * @return true This is a text link. | |
| 115 | */ | |
| 116 | public boolean hasText() { | |
| 117 | return !getText().isEmpty(); | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 121 | * Answers whether a title (tooltip) has been set for the hyperlink. | |
| 122 | * | |
| 123 | * @return true There is a title. | |
| 124 | */ | |
| 125 | public boolean hasTitle() { | |
| 126 | return !getTitle().isEmpty(); | |
| 127 | } | |
| 128 | ||
| 129 | public String getText() { | |
| 130 | return this.text; | |
| 131 | } | |
| 132 | ||
| 133 | public String getUrl() { | |
| 134 | return this.url; | |
| 135 | } | |
| 136 | ||
| 137 | public String getTitle() { | |
| 138 | return this.title; | |
| 139 | } | |
| 140 | ||
| 141 | private String nullSafe( final String s ) { | |
| 142 | return s == null ? "" : s; | |
| 143 | } | |
| 144 | } | |
| 1 | 145 |
| 1 | /* | |
| 2 | * Copyright 2016 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.editor; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.ast.Link; | |
| 31 | import com.vladsch.flexmark.ast.Node; | |
| 32 | import com.vladsch.flexmark.ast.NodeVisitor; | |
| 33 | import com.vladsch.flexmark.ast.VisitHandler; | |
| 34 | ||
| 35 | /** | |
| 36 | * @author White Magic Software, Ltd. | |
| 37 | */ | |
| 38 | public class LinkVisitor { | |
| 39 | ||
| 40 | private NodeVisitor visitor; | |
| 41 | private Link link; | |
| 42 | private final int offset; | |
| 43 | ||
| 44 | /** | |
| 45 | * Creates a hyperlink given an offset into a paragraph and the markdown AST | |
| 46 | * link node. | |
| 47 | * | |
| 48 | * @param index Index into the paragraph that indicates the hyperlink to | |
| 49 | * change. | |
| 50 | */ | |
| 51 | public LinkVisitor( final int index ) { | |
| 52 | this.offset = index; | |
| 53 | } | |
| 54 | ||
| 55 | public Link process( final Node root ) { | |
| 56 | getVisitor().visit( root ); | |
| 57 | return getLink(); | |
| 58 | } | |
| 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 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.editor; | |
| 29 | ||
| 30 | import static com.scrivenvar.Constants.STYLESHEET_EDITOR; | |
| 31 | import com.scrivenvar.dialogs.ImageDialog; | |
| 32 | import com.scrivenvar.dialogs.LinkDialog; | |
| 33 | import com.scrivenvar.processors.MarkdownProcessor; | |
| 34 | import com.scrivenvar.util.Utils; | |
| 35 | import com.vladsch.flexmark.ast.Link; | |
| 36 | import com.vladsch.flexmark.ast.Node; | |
| 37 | import java.nio.file.Path; | |
| 38 | import java.util.regex.Matcher; | |
| 39 | import java.util.regex.Pattern; | |
| 40 | import javafx.beans.value.ObservableValue; | |
| 41 | import javafx.scene.control.Dialog; | |
| 42 | import javafx.scene.control.IndexRange; | |
| 43 | import static javafx.scene.input.KeyCode.ENTER; | |
| 44 | import javafx.scene.input.KeyEvent; | |
| 45 | import javafx.stage.Window; | |
| 46 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 47 | import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | |
| 48 | ||
| 49 | /** | |
| 50 | * Markdown editor pane. | |
| 51 | * | |
| 52 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 53 | */ | |
| 54 | public class MarkdownEditorPane extends EditorPane { | |
| 55 | ||
| 56 | private static final Pattern AUTO_INDENT_PATTERN = Pattern.compile( | |
| 57 | "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" ); | |
| 58 | ||
| 59 | public MarkdownEditorPane() { | |
| 60 | initEditor(); | |
| 61 | } | |
| 62 | ||
| 63 | private void initEditor() { | |
| 64 | final StyleClassedTextArea textArea = getEditor(); | |
| 65 | ||
| 66 | textArea.setWrapText( true ); | |
| 67 | textArea.getStyleClass().add( "markdown-editor" ); | |
| 68 | textArea.getStylesheets().add( STYLESHEET_EDITOR ); | |
| 69 | ||
| 70 | addEventListener( keyPressed( ENTER ), this::enterPressed ); | |
| 71 | ||
| 72 | // TODO: Wait for implementation that allows cutting lines, not paragraphs. | |
| 73 | // addEventListener( keyPressed( X, SHORTCUT_DOWN ), this::cutLine ); | |
| 74 | } | |
| 75 | ||
| 76 | public ObservableValue<String> markdownProperty() { | |
| 77 | return getEditor().textProperty(); | |
| 78 | } | |
| 79 | ||
| 80 | private void enterPressed( final KeyEvent e ) { | |
| 81 | final StyleClassedTextArea textArea = getEditor(); | |
| 82 | final String currentLine = textArea.getText( textArea.getCurrentParagraph() ); | |
| 83 | final Matcher matcher = AUTO_INDENT_PATTERN.matcher( currentLine ); | |
| 84 | ||
| 85 | String newText = "\n"; | |
| 86 | ||
| 87 | if( matcher.matches() ) { | |
| 88 | if( !matcher.group( 2 ).isEmpty() ) { | |
| 89 | // indent new line with same whitespace characters and list markers as current line | |
| 90 | newText = newText.concat( matcher.group( 1 ) ); | |
| 91 | } else { | |
| 92 | // current line contains only whitespace characters and list markers | |
| 93 | // --> empty current line | |
| 94 | final int caretPosition = textArea.getCaretPosition(); | |
| 95 | textArea.selectRange( caretPosition - currentLine.length(), caretPosition ); | |
| 96 | } | |
| 97 | } | |
| 98 | ||
| 99 | textArea.replaceSelection( newText ); | |
| 100 | } | |
| 101 | ||
| 102 | public void surroundSelection( final String leading, final String trailing ) { | |
| 103 | surroundSelection( leading, trailing, null ); | |
| 104 | } | |
| 105 | ||
| 106 | public void surroundSelection( String leading, String trailing, final String hint ) { | |
| 107 | final StyleClassedTextArea textArea = getEditor(); | |
| 108 | ||
| 109 | // Note: not using textArea.insertText() to insert leading and trailing | |
| 110 | // because this would add two changes to undo history | |
| 111 | final IndexRange selection = textArea.getSelection(); | |
| 112 | int start = selection.getStart(); | |
| 113 | int end = selection.getEnd(); | |
| 114 | ||
| 115 | final String selectedText = textArea.getSelectedText(); | |
| 116 | ||
| 117 | // remove leading and trailing whitespaces from selected text | |
| 118 | String trimmedSelectedText = selectedText.trim(); | |
| 119 | if( trimmedSelectedText.length() < selectedText.length() ) { | |
| 120 | start += selectedText.indexOf( trimmedSelectedText ); | |
| 121 | end = start + trimmedSelectedText.length(); | |
| 122 | } | |
| 123 | ||
| 124 | // remove leading whitespaces from leading text if selection starts at zero | |
| 125 | if( start == 0 ) { | |
| 126 | leading = Utils.ltrim( leading ); | |
| 127 | } | |
| 128 | ||
| 129 | // remove trailing whitespaces from trailing text if selection ends at text end | |
| 130 | if( end == textArea.getLength() ) { | |
| 131 | trailing = Utils.rtrim( trailing ); | |
| 132 | } | |
| 133 | ||
| 134 | // remove leading line separators from leading text | |
| 135 | // if there are line separators before the selected text | |
| 136 | if( leading.startsWith( "\n" ) ) { | |
| 137 | for( int i = start - 1; i >= 0 && leading.startsWith( "\n" ); i-- ) { | |
| 138 | if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) { | |
| 139 | break; | |
| 140 | } | |
| 141 | leading = leading.substring( 1 ); | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | // remove trailing line separators from trailing or leading text | |
| 146 | // if there are line separators after the selected text | |
| 147 | final boolean trailingIsEmpty = trailing.isEmpty(); | |
| 148 | String str = trailingIsEmpty ? leading : trailing; | |
| 149 | ||
| 150 | if( str.endsWith( "\n" ) ) { | |
| 151 | final int length = textArea.getLength(); | |
| 152 | ||
| 153 | for( int i = end; i < length && str.endsWith( "\n" ); i++ ) { | |
| 154 | if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) { | |
| 155 | break; | |
| 156 | } | |
| 157 | ||
| 158 | str = str.substring( 0, str.length() - 1 ); | |
| 159 | } | |
| 160 | ||
| 161 | if( trailingIsEmpty ) { | |
| 162 | leading = str; | |
| 163 | } else { | |
| 164 | trailing = str; | |
| 165 | } | |
| 166 | } | |
| 167 | ||
| 168 | int selStart = start + leading.length(); | |
| 169 | int selEnd = end + leading.length(); | |
| 170 | ||
| 171 | // insert hint text if selection is empty | |
| 172 | if( hint != null && trimmedSelectedText.isEmpty() ) { | |
| 173 | trimmedSelectedText = hint; | |
| 174 | selEnd = selStart + hint.length(); | |
| 175 | } | |
| 176 | ||
| 177 | // prevent undo merging with previous text entered by user | |
| 178 | getUndoManager().preventMerge(); | |
| 179 | ||
| 180 | // replace text and update selection | |
| 181 | textArea.replaceText( start, end, leading + trimmedSelectedText + trailing ); | |
| 182 | textArea.selectRange( selStart, selEnd ); | |
| 183 | } | |
| 184 | ||
| 185 | /** | |
| 186 | * Returns one of: selected text, word under cursor, or parsed hyperlink from | |
| 187 | * the markdown AST. | |
| 188 | * | |
| 189 | * @return | |
| 190 | */ | |
| 191 | private HyperlinkModel getHyperlink() { | |
| 192 | final StyleClassedTextArea textArea = getEditor(); | |
| 193 | final String selectedText = textArea.getSelectedText(); | |
| 194 | ||
| 195 | // Get the current paragraph, convert to Markdown nodes. | |
| 196 | final MarkdownProcessor mp = new MarkdownProcessor( null ); | |
| 197 | final int p = textArea.getCurrentParagraph(); | |
| 198 | final String paragraph = textArea.getText( p ); | |
| 199 | final Node node = mp.toNode( paragraph ); | |
| 200 | final LinkVisitor visitor = new LinkVisitor( textArea.getCaretColumn() ); | |
| 201 | final Link link = visitor.process( node ); | |
| 202 | ||
| 203 | if( link != null ) { | |
| 204 | textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() ); | |
| 205 | } | |
| 206 | ||
| 207 | final HyperlinkModel model = createHyperlinkModel( | |
| 208 | link, selectedText, "https://website.com" | |
| 209 | ); | |
| 210 | ||
| 211 | return model; | |
| 212 | } | |
| 213 | ||
| 214 | private HyperlinkModel createHyperlinkModel( | |
| 215 | final Link link, final String selection, final String url ) { | |
| 216 | ||
| 217 | return link == null | |
| 218 | ? new HyperlinkModel( selection, url ) | |
| 219 | : new HyperlinkModel( link ); | |
| 220 | } | |
| 221 | ||
| 222 | private Path getParentPath() { | |
| 223 | final Path parentPath = getPath(); | |
| 224 | return (parentPath != null) ? parentPath.getParent() : null; | |
| 225 | } | |
| 226 | ||
| 227 | private Dialog<String> createLinkDialog() { | |
| 228 | return new LinkDialog( getWindow(), getHyperlink(), getParentPath() ); | |
| 229 | } | |
| 230 | ||
| 231 | private Dialog<String> createImageDialog() { | |
| 232 | return new ImageDialog( getWindow(), getParentPath() ); | |
| 233 | } | |
| 234 | ||
| 235 | private void insertObject( final Dialog<String> dialog ) { | |
| 236 | dialog.showAndWait().ifPresent( result -> { | |
| 237 | getEditor().replaceSelection( result ); | |
| 238 | } ); | |
| 239 | } | |
| 240 | ||
| 241 | public void insertLink() { | |
| 242 | insertObject( createLinkDialog() ); | |
| 243 | } | |
| 244 | ||
| 245 | public void insertImage() { | |
| 246 | insertObject( createImageDialog() ); | |
| 247 | } | |
| 248 | ||
| 249 | private Window getWindow() { | |
| 250 | return getScrollPane().getScene().getWindow(); | |
| 251 | } | |
| 252 | } | |
| 1 | 253 |
| 1 | /* | |
| 2 | * Copyright 2016 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.editor; | |
| 29 | ||
| 30 | import static com.scrivenvar.Constants.SEPARATOR; | |
| 31 | import com.scrivenvar.FileEditorTabPane; | |
| 32 | import com.scrivenvar.Services; | |
| 33 | import com.scrivenvar.decorators.VariableDecorator; | |
| 34 | import com.scrivenvar.decorators.YamlVariableDecorator; | |
| 35 | import com.scrivenvar.definition.DefinitionPane; | |
| 36 | import static com.scrivenvar.definition.Lists.getFirst; | |
| 37 | import static com.scrivenvar.definition.Lists.getLast; | |
| 38 | import com.scrivenvar.service.Settings; | |
| 39 | import com.scrivenvar.ui.VariableTreeItem; | |
| 40 | import static java.lang.Character.isSpaceChar; | |
| 41 | import static java.lang.Character.isWhitespace; | |
| 42 | import static java.lang.Math.min; | |
| 43 | import java.util.function.Consumer; | |
| 44 | import javafx.collections.ObservableList; | |
| 45 | import javafx.event.Event; | |
| 46 | import javafx.scene.control.IndexRange; | |
| 47 | import javafx.scene.control.TreeItem; | |
| 48 | import javafx.scene.input.InputEvent; | |
| 49 | import javafx.scene.input.KeyCode; | |
| 50 | import static javafx.scene.input.KeyCode.AT; | |
| 51 | import static javafx.scene.input.KeyCode.DIGIT2; | |
| 52 | import static javafx.scene.input.KeyCode.ENTER; | |
| 53 | import static javafx.scene.input.KeyCode.MINUS; | |
| 54 | import static javafx.scene.input.KeyCode.SPACE; | |
| 55 | import static javafx.scene.input.KeyCombination.CONTROL_DOWN; | |
| 56 | import static javafx.scene.input.KeyCombination.SHIFT_DOWN; | |
| 57 | import javafx.scene.input.KeyEvent; | |
| 58 | import org.fxmisc.richtext.StyledTextArea; | |
| 59 | import org.fxmisc.wellbehaved.event.EventPattern; | |
| 60 | import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | |
| 61 | import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped; | |
| 62 | import org.fxmisc.wellbehaved.event.InputMap; | |
| 63 | import static org.fxmisc.wellbehaved.event.InputMap.consume; | |
| 64 | import static org.fxmisc.wellbehaved.event.InputMap.sequence; | |
| 65 | ||
| 66 | /** | |
| 67 | * Provides the logic for injecting variable names within the editor. | |
| 68 | * | |
| 69 | * @author White Magic Software, Ltd. | |
| 70 | */ | |
| 71 | public class VariableNameInjector { | |
| 72 | ||
| 73 | public static final int DEFAULT_MAX_VAR_LENGTH = 64; | |
| 74 | ||
| 75 | private static final int NO_DIFFERENCE = -1; | |
| 76 | ||
| 77 | private final Settings settings = Services.load( Settings.class ); | |
| 78 | ||
| 79 | /** | |
| 80 | * Used to capture keyboard events once the user presses @. | |
| 81 | */ | |
| 82 | private InputMap<InputEvent> keyboardMap; | |
| 83 | ||
| 84 | private FileEditorTabPane fileEditorPane; | |
| 85 | private DefinitionPane definitionPane; | |
| 86 | ||
| 87 | /** | |
| 88 | * Position of the variable in the text when in variable mode (0 by default). | |
| 89 | */ | |
| 90 | private int initialCaretPosition; | |
| 91 | ||
| 92 | public VariableNameInjector( | |
| 93 | final FileEditorTabPane editorPane, | |
| 94 | final DefinitionPane definitionPane ) { | |
| 95 | setFileEditorPane( editorPane ); | |
| 96 | setDefinitionPane( definitionPane ); | |
| 97 | ||
| 98 | initKeyboardEventListeners(); | |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Traps keys for performing various short-cut tasks, such as @-mode variable | |
| 103 | * insertion and control+space for variable autocomplete. | |
| 104 | * | |
| 105 | * @ key is pressed, a new keyboard map is inserted in place of the current | |
| 106 | * map -- this class goes into "variable edit mode" (a.k.a. vMode). | |
| 107 | * | |
| 108 | * @see createKeyboardMap() | |
| 109 | */ | |
| 110 | private void initKeyboardEventListeners() { | |
| 111 | addEventListener( keyPressed( SPACE, CONTROL_DOWN ), this::autocomplete ); | |
| 112 | ||
| 113 | // @ key in Linux? | |
| 114 | addEventListener( keyPressed( DIGIT2, SHIFT_DOWN ), this::vMode ); | |
| 115 | // @ key in Windows. | |
| 116 | addEventListener( keyPressed( AT ), this::vMode ); | |
| 117 | } | |
| 118 | ||
| 119 | /** | |
| 120 | * The @ symbol is a short-cut to inserting a YAML variable reference. | |
| 121 | * | |
| 122 | * @param e Superfluous information about the key that was pressed. | |
| 123 | */ | |
| 124 | private void vMode( KeyEvent e ) { | |
| 125 | setInitialCaretPosition(); | |
| 126 | vModeStart(); | |
| 127 | vModeAutocomplete(); | |
| 128 | } | |
| 129 | ||
| 130 | /** | |
| 131 | * Receives key presses until the user completes the variable selection. This | |
| 132 | * allows the arrow keys to be used for selecting variables. | |
| 133 | * | |
| 134 | * @param e The key that was pressed. | |
| 135 | */ | |
| 136 | private void vModeKeyPressed( KeyEvent e ) { | |
| 137 | final KeyCode keyCode = e.getCode(); | |
| 138 | ||
| 139 | switch( keyCode ) { | |
| 140 | case BACK_SPACE: | |
| 141 | // Don't decorate the variable upon exiting vMode. | |
| 142 | vModeBackspace(); | |
| 143 | break; | |
| 144 | ||
| 145 | case ESCAPE: | |
| 146 | // Don't decorate the variable upon exiting vMode. | |
| 147 | vModeStop(); | |
| 148 | break; | |
| 149 | ||
| 150 | case ENTER: | |
| 151 | case PERIOD: | |
| 152 | case RIGHT: | |
| 153 | case END: | |
| 154 | // Stop at a leaf node, ENTER means accept. | |
| 155 | if( vModeConditionalComplete() && keyCode == ENTER ) { | |
| 156 | vModeStop(); | |
| 157 | ||
| 158 | // Decorate the variable upon exiting vMode. | |
| 159 | decorateVariable(); | |
| 160 | } | |
| 161 | break; | |
| 162 | ||
| 163 | case UP: | |
| 164 | cyclePathPrev(); | |
| 165 | break; | |
| 166 | ||
| 167 | case DOWN: | |
| 168 | cyclePathNext(); | |
| 169 | break; | |
| 170 | ||
| 171 | default: | |
| 172 | vModeFilterKeyPressed( e ); | |
| 173 | break; | |
| 174 | } | |
| 175 | ||
| 176 | e.consume(); | |
| 177 | } | |
| 178 | ||
| 179 | private void vModeBackspace() { | |
| 180 | deleteSelection(); | |
| 181 | ||
| 182 | // Break out of variable mode by back spacing to the original position. | |
| 183 | if( getCurrentCaretPosition() > getInitialCaretPosition() ) { | |
| 184 | vModeAutocomplete(); | |
| 185 | } else { | |
| 186 | vModeStop(); | |
| 187 | } | |
| 188 | } | |
| 189 | ||
| 190 | /** | |
| 191 | * Updates the text with the path selected (or typed) by the user. | |
| 192 | */ | |
| 193 | private void vModeAutocomplete() { | |
| 194 | final TreeItem<String> node = getCurrentNode(); | |
| 195 | ||
| 196 | if( !node.isLeaf() ) { | |
| 197 | final String word = getLastPathWord(); | |
| 198 | final String label = node.getValue(); | |
| 199 | final int delta = difference( label, word ); | |
| 200 | final String remainder = delta == NO_DIFFERENCE | |
| 201 | ? label | |
| 202 | : label.substring( delta ); | |
| 203 | ||
| 204 | final StyledTextArea textArea = getEditor(); | |
| 205 | final int posBegan = getCurrentCaretPosition(); | |
| 206 | final int posEnded = posBegan + remainder.length(); | |
| 207 | ||
| 208 | textArea.replaceSelection( remainder ); | |
| 209 | ||
| 210 | if( posEnded - posBegan > 0 ) { | |
| 211 | textArea.selectRange( posEnded, posBegan ); | |
| 212 | } | |
| 213 | ||
| 214 | expand( node ); | |
| 215 | } | |
| 216 | } | |
| 217 | ||
| 218 | /** | |
| 219 | * Only variable name keys can pass through the filter. This is called when | |
| 220 | * the user presses a key. | |
| 221 | * | |
| 222 | * @param e The key that was pressed. | |
| 223 | */ | |
| 224 | private void vModeFilterKeyPressed( final KeyEvent e ) { | |
| 225 | if( isVariableNameKey( e ) ) { | |
| 226 | typed( e.getText() ); | |
| 227 | } | |
| 228 | } | |
| 229 | ||
| 230 | /** | |
| 231 | * Performs an autocomplete depending on whether the user has finished typing | |
| 232 | * in a word. If there is a selected range, then this will complete the most | |
| 233 | * recent word and jump to the next child. | |
| 234 | * | |
| 235 | * @return true The auto-completed node was a terminal node. | |
| 236 | */ | |
| 237 | private boolean vModeConditionalComplete() { | |
| 238 | acceptPath(); | |
| 239 | ||
| 240 | final TreeItem<String> node = getCurrentNode(); | |
| 241 | final boolean terminal = isTerminal( node ); | |
| 242 | ||
| 243 | if( !terminal ) { | |
| 244 | typed( SEPARATOR ); | |
| 245 | } | |
| 246 | ||
| 247 | return terminal; | |
| 248 | } | |
| 249 | ||
| 250 | /** | |
| 251 | * Pressing control+space will find a node that matches the current word and | |
| 252 | * substitute the YAML variable reference. This is called when the user is not | |
| 253 | * editing in vMode. | |
| 254 | * | |
| 255 | * @param e Ignored -- it can only be Ctrl+Space. | |
| 256 | */ | |
| 257 | private void autocomplete( KeyEvent e ) { | |
| 258 | final String paragraph = getCaretParagraph(); | |
| 259 | final int[] boundaries = getWordBoundaries( paragraph ); | |
| 260 | final String word = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] ); | |
| 261 | ||
| 262 | final VariableTreeItem<String> leaf = findLeaf( word ); | |
| 263 | ||
| 264 | if( leaf != null ) { | |
| 265 | replaceText( boundaries[ 0 ], boundaries[ 1 ], leaf.toPath() ); | |
| 266 | decorateVariable(); | |
| 267 | expand( leaf ); | |
| 268 | } | |
| 269 | } | |
| 270 | ||
| 271 | /** | |
| 272 | * Called when autocomplete finishes on a valid leaf or when the user presses | |
| 273 | * Enter to finish manual autocomplete. | |
| 274 | */ | |
| 275 | private void decorateVariable() { | |
| 276 | // A little bit of duplication... | |
| 277 | final String paragraph = getCaretParagraph(); | |
| 278 | final int[] boundaries = getWordBoundaries( paragraph ); | |
| 279 | final String old = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] ); | |
| 280 | ||
| 281 | final String newVariable = getVariableDecorator().decorate( old ); | |
| 282 | ||
| 283 | final int posEnded = getCurrentCaretPosition(); | |
| 284 | final int posBegan = posEnded - old.length(); | |
| 285 | ||
| 286 | getEditor().replaceText( posBegan, posEnded, newVariable ); | |
| 287 | } | |
| 288 | ||
| 289 | /** | |
| 290 | * Updates the text at the given position within the current paragraph. | |
| 291 | * | |
| 292 | * @param posBegan The starting index in the paragraph text to replace. | |
| 293 | * @param posEnded The ending index in the paragraph text to replace. | |
| 294 | * @param text Overwrite the paragraph substring with this text. | |
| 295 | */ | |
| 296 | private void replaceText( | |
| 297 | final int posBegan, final int posEnded, final String text ) { | |
| 298 | final int p = getCurrentParagraph(); | |
| 299 | ||
| 300 | getEditor().replaceText( p, posBegan, p, posEnded, text ); | |
| 301 | } | |
| 302 | ||
| 303 | /** | |
| 304 | * Returns the caret's current paragraph position. | |
| 305 | * | |
| 306 | * @return A number greater than or equal to 0. | |
| 307 | */ | |
| 308 | private int getCurrentParagraph() { | |
| 309 | return getEditor().getCurrentParagraph(); | |
| 310 | } | |
| 311 | ||
| 312 | /** | |
| 313 | * Returns current word boundary indexes into the current paragraph, including | |
| 314 | * punctuation. | |
| 315 | * | |
| 316 | * @param p The paragraph wherein to hunt word boundaries. | |
| 317 | * @param offset The offset into the paragraph to begin scanning left and | |
| 318 | * right. | |
| 319 | * | |
| 320 | * @return The starting and ending index of the word closest to the caret. | |
| 321 | */ | |
| 322 | private int[] getWordBoundaries( final String p, final int offset ) { | |
| 323 | // Remove dashes, but retain hyphens. Retain same number of characters | |
| 324 | // to preserve relative indexes. | |
| 325 | final String paragraph = p.replace( "---", " " ).replace( "--", " " ); | |
| 326 | ||
| 327 | return getWordAt( paragraph, offset ); | |
| 328 | } | |
| 329 | ||
| 330 | /** | |
| 331 | * Helper method to get the word boundaries for the current paragraph. | |
| 332 | * | |
| 333 | * @param paragraph | |
| 334 | * | |
| 335 | * @return | |
| 336 | */ | |
| 337 | private int[] getWordBoundaries( final String paragraph ) { | |
| 338 | return getWordBoundaries( paragraph, getCurrentCaretColumn() ); | |
| 339 | } | |
| 340 | ||
| 341 | /** | |
| 342 | * Given an arbitrary offset into a string, this returns the word at that | |
| 343 | * index. The inputs and outputs include: | |
| 344 | * | |
| 345 | * <ul> | |
| 346 | * <li>surrounded by space: <code>hello | world!</code> ("");</li> | |
| 347 | * <li>end of word: <code>hello| world!</code> ("hello");</li> | |
| 348 | * <li>start of a word: <code>hello |world!</code> ("world!");</li> | |
| 349 | * <li>within a word: <code>hello wo|rld!</code> ("world!");</li> | |
| 350 | * <li>end of a paragraph: <code>hello world!|</code> ("world!");</li> | |
| 351 | * <li>start of a paragraph: <code>|hello world!</code> ("hello!"); or</li> | |
| 352 | * <li>after punctuation: <code>hello world!|</code> ("world!").</li> | |
| 353 | * </ul> | |
| 354 | * | |
| 355 | * @param p The string to scan for a word. | |
| 356 | * @param offset The offset within s to begin searching for the nearest word | |
| 357 | * boundary, must not be out of bounds of s. | |
| 358 | * | |
| 359 | * @return The word in s at the offset. | |
| 360 | * | |
| 361 | * @see getWordBegan( String, int ) | |
| 362 | * @see getWordEnded( String, int ) | |
| 363 | */ | |
| 364 | private int[] getWordAt( final String p, final int offset ) { | |
| 365 | return new int[]{ getWordBegan( p, offset ), getWordEnded( p, offset ) }; | |
| 366 | } | |
| 367 | ||
| 368 | /** | |
| 369 | * Returns the index into s where a word begins. | |
| 370 | * | |
| 371 | * @param s Never null. | |
| 372 | * @param offset Index into s to begin searching backwards for a word | |
| 373 | * boundary. | |
| 374 | * | |
| 375 | * @return The index where a word begins. | |
| 376 | */ | |
| 377 | private int getWordBegan( final String s, int offset ) { | |
| 378 | while( offset > 0 && isBoundary( s.charAt( offset - 1 ) ) ) { | |
| 379 | offset--; | |
| 380 | } | |
| 381 | ||
| 382 | return offset; | |
| 383 | } | |
| 384 | ||
| 385 | /** | |
| 386 | * Returns the index into s where a word ends. | |
| 387 | * | |
| 388 | * @param s Never null. | |
| 389 | * @param offset Index into s to begin searching forwards for a word boundary. | |
| 390 | * | |
| 391 | * @return The index where a word ends. | |
| 392 | */ | |
| 393 | private int getWordEnded( final String s, int offset ) { | |
| 394 | final int length = s.length(); | |
| 395 | ||
| 396 | while( offset < length && isBoundary( s.charAt( offset ) ) ) { | |
| 397 | offset++; | |
| 398 | } | |
| 399 | ||
| 400 | return offset; | |
| 401 | } | |
| 402 | ||
| 403 | /** | |
| 404 | * Returns true if the given character can be reasonably expected to be part | |
| 405 | * of a word, including punctuation marks. | |
| 406 | * | |
| 407 | * @param c The character to compare. | |
| 408 | * | |
| 409 | * @return false The character is a space character. | |
| 410 | */ | |
| 411 | private boolean isBoundary( final char c ) { | |
| 412 | return !isSpaceChar( c ); | |
| 413 | } | |
| 414 | ||
| 415 | /** | |
| 416 | * Returns the text for the paragraph that contains the caret. | |
| 417 | * | |
| 418 | * @return A non-null string, possibly empty. | |
| 419 | */ | |
| 420 | private String getCaretParagraph() { | |
| 421 | return getEditor().getText( getCurrentParagraph() ); | |
| 422 | } | |
| 423 | ||
| 424 | /** | |
| 425 | * Returns true if the node has children that can be selected (i.e., any | |
| 426 | * non-leaves). | |
| 427 | * | |
| 428 | * @param <T> The type that the TreeItem contains. | |
| 429 | * @param node The node to test for terminality. | |
| 430 | * | |
| 431 | * @return true The node has one branch and its a leaf. | |
| 432 | */ | |
| 433 | private <T> boolean isTerminal( final TreeItem<T> node ) { | |
| 434 | final ObservableList<TreeItem<T>> branches = node.getChildren(); | |
| 435 | ||
| 436 | return branches.size() == 1 && branches.get( 0 ).isLeaf(); | |
| 437 | } | |
| 438 | ||
| 439 | /** | |
| 440 | * Inserts text that the user typed at the current caret position, then | |
| 441 | * performs an autocomplete for the variable name. | |
| 442 | * | |
| 443 | * @param text The text to insert, never null. | |
| 444 | */ | |
| 445 | private void typed( final String text ) { | |
| 446 | getEditor().replaceSelection( text ); | |
| 447 | vModeAutocomplete(); | |
| 448 | } | |
| 449 | ||
| 450 | /** | |
| 451 | * Called when the user presses either End or Enter key. | |
| 452 | */ | |
| 453 | private void acceptPath() { | |
| 454 | final IndexRange range = getSelectionRange(); | |
| 455 | ||
| 456 | if( range != null ) { | |
| 457 | final int rangeEnd = range.getEnd(); | |
| 458 | final StyledTextArea textArea = getEditor(); | |
| 459 | textArea.deselect(); | |
| 460 | textArea.moveTo( rangeEnd ); | |
| 461 | } | |
| 462 | } | |
| 463 | ||
| 464 | /** | |
| 465 | * Replaces the entirety of the existing path (from the initial caret | |
| 466 | * position) with the given path. | |
| 467 | * | |
| 468 | * @param oldPath The path to replace. | |
| 469 | * @param newPath The replacement path. | |
| 470 | */ | |
| 471 | private void replacePath( final String oldPath, final String newPath ) { | |
| 472 | final StyledTextArea textArea = getEditor(); | |
| 473 | final int posBegan = getInitialCaretPosition(); | |
| 474 | final int posEnded = posBegan + oldPath.length(); | |
| 475 | ||
| 476 | textArea.deselect(); | |
| 477 | textArea.replaceText( posBegan, posEnded, newPath ); | |
| 478 | } | |
| 479 | ||
| 480 | /** | |
| 481 | * Called when the user presses the Backspace key. | |
| 482 | */ | |
| 483 | private void deleteSelection() { | |
| 484 | final StyledTextArea textArea = getEditor(); | |
| 485 | textArea.replaceSelection( "" ); | |
| 486 | textArea.deletePreviousChar(); | |
| 487 | } | |
| 488 | ||
| 489 | /** | |
| 490 | * Cycles the selected text through the nodes. | |
| 491 | * | |
| 492 | * @param direction true - next; false - previous | |
| 493 | */ | |
| 494 | private void cycleSelection( final boolean direction ) { | |
| 495 | final TreeItem<String> node = getCurrentNode(); | |
| 496 | ||
| 497 | // Find the sibling for the current selection and replace the current | |
| 498 | // selection with the sibling's value | |
| 499 | TreeItem< String> cycled = direction | |
| 500 | ? node.nextSibling() | |
| 501 | : node.previousSibling(); | |
| 502 | ||
| 503 | // When cycling at the end (or beginning) of the list, jump to the first | |
| 504 | // (or last) sibling depending on the cycle direction. | |
| 505 | if( cycled == null ) { | |
| 506 | cycled = direction ? getFirstSibling( node ) : getLastSibling( node ); | |
| 507 | } | |
| 508 | ||
| 509 | final String path = getCurrentPath(); | |
| 510 | final String cycledWord = cycled.getValue(); | |
| 511 | final String word = getLastPathWord(); | |
| 512 | final int index = path.indexOf( word ); | |
| 513 | final String cycledPath = path.substring( 0, index ) + cycledWord; | |
| 514 | ||
| 515 | expand( cycled ); | |
| 516 | replacePath( path, cycledPath ); | |
| 517 | } | |
| 518 | ||
| 519 | /** | |
| 520 | * Cycles to the next sibling of the currently selected tree node. | |
| 521 | */ | |
| 522 | private void cyclePathNext() { | |
| 523 | cycleSelection( true ); | |
| 524 | } | |
| 525 | ||
| 526 | /** | |
| 527 | * Cycles to the previous sibling of the currently selected tree node. | |
| 528 | */ | |
| 529 | private void cyclePathPrev() { | |
| 530 | cycleSelection( false ); | |
| 531 | } | |
| 532 | ||
| 533 | /** | |
| 534 | * Returns the variable name (or as much as has been typed so far). Returns | |
| 535 | * all the characters from the initial caret column to the the first | |
| 536 | * whitespace character. This will return a path that contains zero or more | |
| 537 | * separators. | |
| 538 | * | |
| 539 | * @return A non-null string, possibly empty. | |
| 540 | */ | |
| 541 | private String getCurrentPath() { | |
| 542 | final String s = extractTextChunk(); | |
| 543 | final int length = s.length(); | |
| 544 | ||
| 545 | int i = 0; | |
| 546 | ||
| 547 | while( i < length && !isWhitespace( s.charAt( i ) ) ) { | |
| 548 | i++; | |
| 549 | } | |
| 550 | ||
| 551 | return s.substring( 0, i ); | |
| 552 | } | |
| 553 | ||
| 554 | private <T> ObservableList<TreeItem<T>> getSiblings( | |
| 555 | final TreeItem<T> item ) { | |
| 556 | final TreeItem<T> parent = item.getParent(); | |
| 557 | return parent == null ? item.getChildren() : parent.getChildren(); | |
| 558 | } | |
| 559 | ||
| 560 | private <T> TreeItem<T> getFirstSibling( final TreeItem<T> item ) { | |
| 561 | return getFirst( getSiblings( item ), item ); | |
| 562 | } | |
| 563 | ||
| 564 | private <T> TreeItem<T> getLastSibling( final TreeItem<T> item ) { | |
| 565 | return getLast( getSiblings( item ), item ); | |
| 566 | } | |
| 567 | ||
| 568 | /** | |
| 569 | * Returns the caret position as an offset into the text. | |
| 570 | * | |
| 571 | * @return A value from 0 to the length of the text (minus one). | |
| 572 | */ | |
| 573 | private int getCurrentCaretPosition() { | |
| 574 | return getEditor().getCaretPosition(); | |
| 575 | } | |
| 576 | ||
| 577 | /** | |
| 578 | * Returns the caret position within the current paragraph. | |
| 579 | * | |
| 580 | * @return A value from 0 to the length of the current paragraph. | |
| 581 | */ | |
| 582 | private int getCurrentCaretColumn() { | |
| 583 | return getEditor().getCaretColumn(); | |
| 584 | } | |
| 585 | ||
| 586 | /** | |
| 587 | * Returns the last word from the path. | |
| 588 | * | |
| 589 | * @return The last token. | |
| 590 | */ | |
| 591 | private String getLastPathWord() { | |
| 592 | String path = getCurrentPath(); | |
| 593 | ||
| 594 | int i = path.indexOf( SEPARATOR ); | |
| 595 | ||
| 596 | while( i > 0 ) { | |
| 597 | path = path.substring( i + 1 ); | |
| 598 | i = path.indexOf( SEPARATOR ); | |
| 599 | } | |
| 600 | ||
| 601 | return path; | |
| 602 | } | |
| 603 | ||
| 604 | /** | |
| 605 | * Returns text from the initial caret position until some arbitrarily long | |
| 606 | * number of characters. The number of characters extracted will be | |
| 607 | * getMaxVarLength, or fewer, depending on how many characters remain to be | |
| 608 | * extracted. The result from this method is trimmed to the first whitespace | |
| 609 | * character. | |
| 610 | * | |
| 611 | * @return A chunk of text that includes all the words representing a path, | |
| 612 | * and then some. | |
| 613 | */ | |
| 614 | private String extractTextChunk() { | |
| 615 | final StyledTextArea textArea = getEditor(); | |
| 616 | final int textBegan = getInitialCaretPosition(); | |
| 617 | final int remaining = textArea.getLength() - textBegan; | |
| 618 | final int textEnded = min( remaining, getMaxVarLength() ); | |
| 619 | ||
| 620 | return textArea.getText( textBegan, textEnded ); | |
| 621 | } | |
| 622 | ||
| 623 | /** | |
| 624 | * Returns the node for the current path. | |
| 625 | */ | |
| 626 | private TreeItem<String> getCurrentNode() { | |
| 627 | return findNode( getCurrentPath() ); | |
| 628 | } | |
| 629 | ||
| 630 | /** | |
| 631 | * Finds the node that most closely matches the given path. | |
| 632 | * | |
| 633 | * @param path The path that represents a node. | |
| 634 | * | |
| 635 | * @return The node for the path, or the root node if the path could not be | |
| 636 | * found, but never null. | |
| 637 | */ | |
| 638 | private TreeItem<String> findNode( final String path ) { | |
| 639 | return getDefinitionPane().findNode( path ); | |
| 640 | } | |
| 641 | ||
| 642 | /** | |
| 643 | * Finds the first leaf having a value that starts with the given text. | |
| 644 | * | |
| 645 | * @param text The text to find in the definition tree. | |
| 646 | * | |
| 647 | * @return The leaf that starts with the given text, or null if not found. | |
| 648 | */ | |
| 649 | private VariableTreeItem<String> findLeaf( final String text ) { | |
| 650 | return getDefinitionPane().findLeaf( text ); | |
| 651 | } | |
| 652 | ||
| 653 | /** | |
| 654 | * Used to ignore typed keys in favour of trapping pressed keys. | |
| 655 | * | |
| 656 | * @param e The key that was typed. | |
| 657 | */ | |
| 658 | private void vModeKeyTyped( KeyEvent e ) { | |
| 659 | e.consume(); | |
| 660 | } | |
| 661 | ||
| 662 | /** | |
| 663 | * Used to lazily initialize the keyboard map. | |
| 664 | * | |
| 665 | * @return Mappings for keyTyped and keyPressed. | |
| 666 | */ | |
| 667 | protected InputMap<InputEvent> createKeyboardMap() { | |
| 668 | return sequence( | |
| 669 | consume( keyTyped(), this::vModeKeyTyped ), | |
| 670 | consume( keyPressed(), this::vModeKeyPressed ) | |
| 671 | ); | |
| 672 | } | |
| 673 | ||
| 674 | private InputMap<InputEvent> getKeyboardMap() { | |
| 675 | if( this.keyboardMap == null ) { | |
| 676 | this.keyboardMap = createKeyboardMap(); | |
| 677 | } | |
| 678 | ||
| 679 | return this.keyboardMap; | |
| 680 | } | |
| 681 | ||
| 682 | /** | |
| 683 | * Collapses the tree then expands and selects the given node. | |
| 684 | * | |
| 685 | * @param node The node to expand. | |
| 686 | */ | |
| 687 | private void expand( final TreeItem<String> node ) { | |
| 688 | final DefinitionPane pane = getDefinitionPane(); | |
| 689 | pane.collapse(); | |
| 690 | pane.expand( node ); | |
| 691 | pane.select( node ); | |
| 692 | } | |
| 693 | ||
| 694 | /** | |
| 695 | * Returns true iff the key code the user typed can be used as part of a YAML | |
| 696 | * variable name. | |
| 697 | * | |
| 698 | * @param keyEvent Keyboard key press event information. | |
| 699 | * | |
| 700 | * @return true The key is a value that can be inserted into the text. | |
| 701 | */ | |
| 702 | private boolean isVariableNameKey( final KeyEvent keyEvent ) { | |
| 703 | final KeyCode kc = keyEvent.getCode(); | |
| 704 | ||
| 705 | return (kc.isLetterKey() | |
| 706 | || kc.isDigitKey() | |
| 707 | || (keyEvent.isShiftDown() && kc == MINUS)) | |
| 708 | && !keyEvent.isControlDown(); | |
| 709 | } | |
| 710 | ||
| 711 | /** | |
| 712 | * Starts to capture user input events. | |
| 713 | */ | |
| 714 | private void vModeStart() { | |
| 715 | addEventListener( getKeyboardMap() ); | |
| 716 | } | |
| 717 | ||
| 718 | /** | |
| 719 | * Restores capturing of user input events to the previous event listener. | |
| 720 | * Also asks the processing chain to modify the variable text into a | |
| 721 | * machine-readable variable based on the format required by the file type. | |
| 722 | * For example, a Markdown file (.md) will substitute a $VAR$ name while an R | |
| 723 | * file (.Rmd, .Rxml) will use `r#xVAR`. | |
| 724 | */ | |
| 725 | private void vModeStop() { | |
| 726 | removeEventListener( getKeyboardMap() ); | |
| 727 | } | |
| 728 | ||
| 729 | private VariableDecorator getVariableDecorator() { | |
| 730 | return new YamlVariableDecorator(); | |
| 731 | } | |
| 732 | ||
| 733 | /** | |
| 734 | * Returns the index where the two strings diverge. | |
| 735 | * | |
| 736 | * @param s1 The string that could be a substring of s2, null allowed. | |
| 737 | * @param s2 The string that could be a substring of s1, null allowed. | |
| 738 | * | |
| 739 | * @return NO_DIFFERENCE if the strings are the same, otherwise the index | |
| 740 | * where they differ. | |
| 741 | */ | |
| 742 | @SuppressWarnings( "StringEquality" ) | |
| 743 | private int difference( final CharSequence s1, final CharSequence s2 ) { | |
| 744 | if( s1 == s2 ) { | |
| 745 | return NO_DIFFERENCE; | |
| 746 | } | |
| 747 | ||
| 748 | if( s1 == null || s2 == null ) { | |
| 749 | return 0; | |
| 750 | } | |
| 751 | ||
| 752 | int i = 0; | |
| 753 | final int limit = min( s1.length(), s2.length() ); | |
| 754 | ||
| 755 | while( i < limit && s1.charAt( i ) == s2.charAt( i ) ) { | |
| 756 | i++; | |
| 757 | } | |
| 758 | ||
| 759 | // If one string was shorter than the other, that's where they differ. | |
| 760 | return i; | |
| 761 | } | |
| 762 | ||
| 763 | /** | |
| 764 | * Delegates to the file editor pane, and, ultimately, to its text area. | |
| 765 | */ | |
| 766 | private <T extends Event, U extends T> void addEventListener( | |
| 767 | final EventPattern<? super T, ? extends U> event, | |
| 768 | final Consumer<? super U> consumer ) { | |
| 769 | getFileEditorPane().addEventListener( event, consumer ); | |
| 770 | } | |
| 771 | ||
| 772 | /** | |
| 773 | * Delegates to the file editor pane, and, ultimately, to its text area. | |
| 774 | * | |
| 775 | * @param map The map of methods to events. | |
| 776 | */ | |
| 777 | private void addEventListener( final InputMap<InputEvent> map ) { | |
| 778 | getFileEditorPane().addEventListener( map ); | |
| 779 | } | |
| 780 | ||
| 781 | private void removeEventListener( final InputMap<InputEvent> map ) { | |
| 782 | getFileEditorPane().removeEventListener( map ); | |
| 783 | } | |
| 784 | ||
| 785 | /** | |
| 786 | * Returns the position of the caret when variable mode editing was requested. | |
| 787 | * | |
| 788 | * @return The variable mode caret position. | |
| 789 | */ | |
| 790 | private int getInitialCaretPosition() { | |
| 791 | return this.initialCaretPosition; | |
| 792 | } | |
| 793 | ||
| 794 | /** | |
| 795 | * Sets the position of the caret when variable mode editing was requested. | |
| 796 | * Stores the current position because only the text that comes afterwards is | |
| 797 | * a suitable variable reference. | |
| 798 | * | |
| 799 | * @return The variable mode caret position. | |
| 800 | */ | |
| 801 | private void setInitialCaretPosition() { | |
| 802 | this.initialCaretPosition = getEditor().getCaretPosition(); | |
| 803 | } | |
| 804 | ||
| 805 | private StyledTextArea getEditor() { | |
| 806 | return getFileEditorPane().getEditor(); | |
| 807 | } | |
| 808 | ||
| 809 | public FileEditorTabPane getFileEditorPane() { | |
| 810 | return this.fileEditorPane; | |
| 811 | } | |
| 812 | ||
| 813 | private void setFileEditorPane( final FileEditorTabPane fileEditorPane ) { | |
| 814 | this.fileEditorPane = fileEditorPane; | |
| 815 | } | |
| 816 | ||
| 817 | private DefinitionPane getDefinitionPane() { | |
| 818 | return this.definitionPane; | |
| 819 | } | |
| 820 | ||
| 821 | private void setDefinitionPane( final DefinitionPane definitionPane ) { | |
| 822 | this.definitionPane = definitionPane; | |
| 823 | } | |
| 824 | ||
| 825 | private IndexRange getSelectionRange() { | |
| 826 | return getEditor().getSelection(); | |
| 827 | } | |
| 828 | ||
| 829 | /** | |
| 830 | * Don't look ahead too far when trying to find the end of a node. | |
| 831 | * | |
| 832 | * @return 512 by default. | |
| 833 | */ | |
| 834 | private int getMaxVarLength() { | |
| 835 | return getSettings().getSetting( | |
| 836 | "editor.variable.maxLength", DEFAULT_MAX_VAR_LENGTH ); | |
| 837 | } | |
| 838 | ||
| 839 | private Settings getSettings() { | |
| 840 | return this.settings; | |
| 841 | } | |
| 842 | } | |
| 1 | 843 |
| 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 | package com.scrivenvar.options; | |
| 28 | ||
| 29 | import com.scrivenvar.Messages; | |
| 30 | import com.scrivenvar.ui.AbstractPane; | |
| 31 | import com.scrivenvar.util.Item; | |
| 32 | import java.nio.charset.Charset; | |
| 33 | import java.util.ArrayList; | |
| 34 | import java.util.Collection; | |
| 35 | import java.util.SortedMap; | |
| 36 | import javafx.scene.control.ComboBox; | |
| 37 | import javafx.scene.control.Label; | |
| 38 | ||
| 39 | /** | |
| 40 | * General options pane. | |
| 41 | * | |
| 42 | * @author Karl Tauber | |
| 43 | */ | |
| 44 | public class GeneralOptionsPane extends AbstractPane { | |
| 45 | ||
| 46 | @SuppressWarnings( "unchecked" ) | |
| 47 | public GeneralOptionsPane() { | |
| 48 | initComponents(); | |
| 49 | ||
| 50 | String defaultLineSeparator = System.getProperty( "line.separator", "\n" ); | |
| 51 | String defaultLineSeparatorStr = defaultLineSeparator.replace( "\r", "CR" ).replace( "\n", "LF" ); | |
| 52 | lineSeparatorField.getItems().addAll( | |
| 53 | new Item<>( Messages.get( "GeneralOptionsPane.platformDefault", defaultLineSeparatorStr ), null ), | |
| 54 | new Item<>( Messages.get( "GeneralOptionsPane.sepWindows" ), "\r\n" ), | |
| 55 | new Item<>( Messages.get( "GeneralOptionsPane.sepUnix" ), "\n" ) ); | |
| 56 | ||
| 57 | encodingField.getItems().addAll( getAvailableEncodings() ); | |
| 58 | } | |
| 59 | ||
| 60 | private Collection<Item<String>> getAvailableEncodings() { | |
| 61 | SortedMap<String, Charset> availableCharsets = Charset.availableCharsets(); | |
| 62 | ||
| 63 | ArrayList<Item<String>> values = new ArrayList<>( 1 + availableCharsets.size() ); | |
| 64 | values.add( new Item<>( Messages.get( "GeneralOptionsPane.platformDefault", Charset.defaultCharset().name() ), null ) ); | |
| 65 | ||
| 66 | for( String name : availableCharsets.keySet() ) { | |
| 67 | values.add( new Item<>( name, name ) ); | |
| 68 | } | |
| 69 | ||
| 70 | return values; | |
| 71 | } | |
| 72 | ||
| 73 | void load() { | |
| 74 | lineSeparatorField.setValue( new Item<>( getOptions().getLineSeparator(), getOptions().getLineSeparator() ) ); | |
| 75 | encodingField.setValue( new Item<>( getOptions().getEncoding(), getOptions().getEncoding() ) ); | |
| 76 | ||
| 77 | } | |
| 78 | ||
| 79 | void save() { | |
| 80 | getOptions().setLineSeparator( lineSeparatorField.getValue().value ); | |
| 81 | getOptions().setEncoding( encodingField.getValue().value ); | |
| 82 | ||
| 83 | } | |
| 84 | ||
| 85 | private void initComponents() { | |
| 86 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 87 | Label lineSeparatorLabel = new Label(); | |
| 88 | lineSeparatorField = new ComboBox<>(); | |
| 89 | Label lineSeparatorLabel2 = new Label(); | |
| 90 | Label encodingLabel = new Label(); | |
| 91 | encodingField = new ComboBox<>(); | |
| 92 | ||
| 93 | //======== this ======== | |
| 94 | setCols( "[fill][fill][fill]" ); | |
| 95 | setRows( "[][]para[]" ); | |
| 96 | ||
| 97 | //---- lineSeparatorLabel ---- | |
| 98 | lineSeparatorLabel.setText( Messages.get( "GeneralOptionsPane.lineSeparatorLabel.text" ) ); | |
| 99 | lineSeparatorLabel.setMnemonicParsing( true ); | |
| 100 | add( lineSeparatorLabel, "cell 0 0" ); | |
| 101 | add( lineSeparatorField, "cell 1 0" ); | |
| 102 | ||
| 103 | //---- lineSeparatorLabel2 ---- | |
| 104 | lineSeparatorLabel2.setText( Messages.get( "GeneralOptionsPane.lineSeparatorLabel2.text" ) ); | |
| 105 | add( lineSeparatorLabel2, "cell 2 0" ); | |
| 106 | ||
| 107 | //---- encodingLabel ---- | |
| 108 | encodingLabel.setText( Messages.get( "GeneralOptionsPane.encodingLabel.text" ) ); | |
| 109 | encodingLabel.setMnemonicParsing( true ); | |
| 110 | add( encodingLabel, "cell 0 1" ); | |
| 111 | ||
| 112 | //---- encodingField ---- | |
| 113 | encodingField.setVisibleRowCount( 20 ); | |
| 114 | add( encodingField, "cell 1 1" ); | |
| 115 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 116 | ||
| 117 | // TODO set this in JFormDesigner as soon as it supports labelFor | |
| 118 | lineSeparatorLabel.setLabelFor( lineSeparatorField ); | |
| 119 | encodingLabel.setLabelFor( encodingField ); | |
| 120 | } | |
| 121 | ||
| 122 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 123 | private ComboBox<Item<String>> lineSeparatorField; | |
| 124 | private ComboBox<Item<String>> encodingField; | |
| 125 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 126 | } | |
| 1 | 127 |
| 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": "GeneralOptionsPane" | |
| 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": "[fill][fill][fill]" | |
| 13 | "$rowConstraints": "[][]para[]" | |
| 14 | } ) { | |
| 15 | name: "this" | |
| 16 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 17 | name: "lineSeparatorLabel" | |
| 18 | "text": new FormMessage( null, "GeneralOptionsPane.lineSeparatorLabel.text" ) | |
| 19 | "mnemonicParsing": true | |
| 20 | auxiliary() { | |
| 21 | "JavaCodeGenerator.variableLocal": true | |
| 22 | } | |
| 23 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 24 | "value": "cell 0 0" | |
| 25 | } ) | |
| 26 | add( new FormComponent( "javafx.scene.control.ComboBox" ) { | |
| 27 | name: "lineSeparatorField" | |
| 28 | auxiliary() { | |
| 29 | "JavaCodeGenerator.typeParameters": "Item<String>" | |
| 30 | } | |
| 31 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 32 | "value": "cell 1 0" | |
| 33 | } ) | |
| 34 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 35 | name: "lineSeparatorLabel2" | |
| 36 | "text": new FormMessage( null, "GeneralOptionsPane.lineSeparatorLabel2.text" ) | |
| 37 | auxiliary() { | |
| 38 | "JavaCodeGenerator.variableLocal": true | |
| 39 | } | |
| 40 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 41 | "value": "cell 2 0" | |
| 42 | } ) | |
| 43 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 44 | name: "encodingLabel" | |
| 45 | "text": new FormMessage( null, "GeneralOptionsPane.encodingLabel.text" ) | |
| 46 | "mnemonicParsing": true | |
| 47 | auxiliary() { | |
| 48 | "JavaCodeGenerator.variableLocal": true | |
| 49 | } | |
| 50 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 51 | "value": "cell 0 1" | |
| 52 | } ) | |
| 53 | add( new FormComponent( "javafx.scene.control.ComboBox" ) { | |
| 54 | name: "encodingField" | |
| 55 | "visibleRowCount": 20 | |
| 56 | auxiliary() { | |
| 57 | "JavaCodeGenerator.typeParameters": "Item<String>" | |
| 58 | } | |
| 59 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 60 | "value": "cell 1 1" | |
| 61 | } ) | |
| 62 | }, new FormLayoutConstraints( null ) { | |
| 63 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 64 | "size": new javafx.geometry.Dimension2D( 400.0, 300.0 ) | |
| 65 | } ) | |
| 66 | } | |
| 67 | } | |
| 1 | 68 |
| 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": "MarkdownOptionsPane" | |
| 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 | "$rowConstraints": "[][][][][][][][][][][][][][][][][][]" | |
| 12 | "$columnConstraints": "[][fill]" | |
| 13 | } ) { | |
| 14 | name: "this" | |
| 15 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 16 | name: "smartsExtCheckBox" | |
| 17 | "text": new FormMessage( null, "MarkdownOptionsPane.smartsExtCheckBox.text" ) | |
| 18 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 19 | "value": "cell 0 0" | |
| 20 | } ) | |
| 21 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 22 | name: "quotesExtCheckBox" | |
| 23 | "text": new FormMessage( null, "MarkdownOptionsPane.quotesExtCheckBox.text" ) | |
| 24 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 25 | "value": "cell 0 1" | |
| 26 | } ) | |
| 27 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 28 | name: "abbreviationsExtCheckBox" | |
| 29 | "text": new FormMessage( null, "MarkdownOptionsPane.abbreviationsExtCheckBox.text" ) | |
| 30 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 31 | "value": "cell 0 2" | |
| 32 | } ) | |
| 33 | add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) { | |
| 34 | name: "abbreviationsExtLink" | |
| 35 | "text": new FormMessage( null, "MarkdownOptionsPane.abbreviationsExtLink.text" ) | |
| 36 | "uri": "http://michelf.com/projects/php-markdown/extra/#abbr" | |
| 37 | auxiliary() { | |
| 38 | "JavaCodeGenerator.variableLocal": true | |
| 39 | } | |
| 40 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 41 | "value": "cell 0 2,gapx 0" | |
| 42 | } ) | |
| 43 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 44 | name: "hardwrapsExtCheckBox" | |
| 45 | "text": new FormMessage( null, "MarkdownOptionsPane.hardwrapsExtCheckBox.text" ) | |
| 46 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 47 | "value": "cell 0 3" | |
| 48 | } ) | |
| 49 | add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) { | |
| 50 | name: "hardwrapsExtLink" | |
| 51 | "text": new FormMessage( null, "MarkdownOptionsPane.hardwrapsExtLink.text" ) | |
| 52 | "uri": "https://help.github.com/articles/writing-on-github/#markup" | |
| 53 | auxiliary() { | |
| 54 | "JavaCodeGenerator.variableLocal": true | |
| 55 | } | |
| 56 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 57 | "value": "cell 0 3,gapx 0" | |
| 58 | } ) | |
| 59 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 60 | name: "autolinksExtCheckBox" | |
| 61 | "text": new FormMessage( null, "MarkdownOptionsPane.autolinksExtCheckBox.text" ) | |
| 62 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 63 | "value": "cell 0 4" | |
| 64 | } ) | |
| 65 | add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) { | |
| 66 | name: "autolinksExtLink" | |
| 67 | "text": new FormMessage( null, "MarkdownOptionsPane.autolinksExtLink.text" ) | |
| 68 | "uri": "https://help.github.com/articles/github-flavored-markdown/#url-autolinking" | |
| 69 | auxiliary() { | |
| 70 | "JavaCodeGenerator.variableLocal": true | |
| 71 | } | |
| 72 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 73 | "value": "cell 0 4,gapx 0" | |
| 74 | } ) | |
| 75 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 76 | name: "tablesExtCheckBox" | |
| 77 | "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtCheckBox.text" ) | |
| 78 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 79 | "value": "cell 0 5" | |
| 80 | } ) | |
| 81 | add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) { | |
| 82 | name: "tablesExtLink" | |
| 83 | "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLink.text" ) | |
| 84 | "uri": "http://fletcher.github.io/MultiMarkdown-4/syntax.html#tables" | |
| 85 | auxiliary() { | |
| 86 | "JavaCodeGenerator.variableLocal": true | |
| 87 | } | |
| 88 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 89 | "value": "cell 0 5,gapx 0" | |
| 90 | } ) | |
| 91 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 92 | name: "tablesExtLabel" | |
| 93 | "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLabel.text" ) | |
| 94 | auxiliary() { | |
| 95 | "JavaCodeGenerator.variableLocal": true | |
| 96 | } | |
| 97 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 98 | "value": "cell 0 5,gapx 3" | |
| 99 | } ) | |
| 100 | add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) { | |
| 101 | name: "tablesExtLink2" | |
| 102 | "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLink2.text" ) | |
| 103 | "uri": "https://michelf.ca/projects/php-markdown/extra/#table" | |
| 104 | auxiliary() { | |
| 105 | "JavaCodeGenerator.variableLocal": true | |
| 106 | } | |
| 107 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 108 | "value": "cell 0 5,gapx 3 3" | |
| 109 | } ) | |
| 110 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 111 | name: "tablesExtLabel2" | |
| 112 | "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLabel2.text" ) | |
| 113 | auxiliary() { | |
| 114 | "JavaCodeGenerator.variableLocal": true | |
| 115 | } | |
| 116 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 117 | "value": "cell 0 5,gapx 0" | |
| 118 | } ) | |
| 119 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 120 | name: "definitionListsExtCheckBox" | |
| 121 | "text": new FormMessage( null, "MarkdownOptionsPane.definitionListsExtCheckBox.text" ) | |
| 122 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 123 | "value": "cell 0 6" | |
| 124 | } ) | |
| 125 | add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) { | |
| 126 | name: "definitionListsExtLink" | |
| 127 | "text": new FormMessage( null, "MarkdownOptionsPane.definitionListsExtLink.text" ) | |
| 128 | "uri": "https://michelf.ca/projects/php-markdown/extra/#def-list" | |
| 129 | auxiliary() { | |
| 130 | "JavaCodeGenerator.variableLocal": true | |
| 131 | } | |
| 132 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 133 | "value": "cell 0 6,gapx 0" | |
| 134 | } ) | |
| 135 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 136 | name: "fencedCodeBlocksExtCheckBox" | |
| 137 | "text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtCheckBox.text" ) | |
| 138 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 139 | "value": "cell 0 7" | |
| 140 | } ) | |
| 141 | add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) { | |
| 142 | name: "fencedCodeBlocksExtLink" | |
| 143 | "text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLink.text" ) | |
| 144 | "uri": "http://michelf.com/projects/php-markdown/extra/#fenced-code-blocks" | |
| 145 | auxiliary() { | |
| 146 | "JavaCodeGenerator.variableLocal": true | |
| 147 | } | |
| 148 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 149 | "value": "cell 0 7,gapx 0" | |
| 150 | } ) | |
| 151 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 152 | name: "fencedCodeBlocksExtLabel" | |
| 153 | "text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLabel.text" ) | |
| 154 | auxiliary() { | |
| 155 | "JavaCodeGenerator.variableLocal": true | |
| 156 | } | |
| 157 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 158 | "value": "cell 0 7,gapx 3" | |
| 159 | } ) | |
| 160 | add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) { | |
| 161 | name: "fencedCodeBlocksExtLink2" | |
| 162 | "text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLink2.text" ) | |
| 163 | "uri": "https://help.github.com/articles/github-flavored-markdown/#fenced-code-blocks" | |
| 164 | auxiliary() { | |
| 165 | "JavaCodeGenerator.variableLocal": true | |
| 166 | } | |
| 167 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 168 | "value": "cell 0 7,gapx 3" | |
| 169 | } ) | |
| 170 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 171 | name: "wikilinksExtCheckBox" | |
| 172 | "text": new FormMessage( null, "MarkdownOptionsPane.wikilinksExtCheckBox.text" ) | |
| 173 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 174 | "value": "cell 0 8" | |
| 175 | } ) | |
| 176 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 177 | name: "strikethroughExtCheckBox" | |
| 178 | "text": new FormMessage( null, "MarkdownOptionsPane.strikethroughExtCheckBox.text" ) | |
| 179 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 180 | "value": "cell 0 9" | |
| 181 | } ) | |
| 182 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 183 | name: "anchorlinksExtCheckBox" | |
| 184 | "text": new FormMessage( null, "MarkdownOptionsPane.anchorlinksExtCheckBox.text" ) | |
| 185 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 186 | "value": "cell 0 10" | |
| 187 | } ) | |
| 188 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 189 | name: "suppressHtmlBlocksExtCheckBox" | |
| 190 | "text": new FormMessage( null, "MarkdownOptionsPane.suppressHtmlBlocksExtCheckBox.text" ) | |
| 191 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 192 | "value": "cell 0 11" | |
| 193 | } ) | |
| 194 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 195 | name: "suppressInlineHtmlExtCheckBox" | |
| 196 | "text": new FormMessage( null, "MarkdownOptionsPane.suppressInlineHtmlExtCheckBox.text" ) | |
| 197 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 198 | "value": "cell 0 12" | |
| 199 | } ) | |
| 200 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 201 | name: "atxHeaderSpaceExtCheckBox" | |
| 202 | "text": new FormMessage( null, "MarkdownOptionsPane.atxHeaderSpaceExtCheckBox.text" ) | |
| 203 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 204 | "value": "cell 0 13" | |
| 205 | } ) | |
| 206 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 207 | name: "forceListItemParaExtCheckBox" | |
| 208 | "text": new FormMessage( null, "MarkdownOptionsPane.forceListItemParaExtCheckBox.text" ) | |
| 209 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 210 | "value": "cell 0 14" | |
| 211 | } ) | |
| 212 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 213 | name: "relaxedHrRulesExtCheckBox" | |
| 214 | "text": new FormMessage( null, "MarkdownOptionsPane.relaxedHrRulesExtCheckBox.text" ) | |
| 215 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 216 | "value": "cell 0 15" | |
| 217 | } ) | |
| 218 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 219 | name: "taskListItemsExtCheckBox" | |
| 220 | "text": new FormMessage( null, "MarkdownOptionsPane.taskListItemsExtCheckBox.text" ) | |
| 221 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 222 | "value": "cell 0 16" | |
| 223 | } ) | |
| 224 | add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) { | |
| 225 | name: "extAnchorLinksExtCheckBox" | |
| 226 | "text": new FormMessage( null, "MarkdownOptionsPane.extAnchorLinksExtCheckBox.text" ) | |
| 227 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 228 | "value": "cell 0 17" | |
| 229 | } ) | |
| 230 | }, new FormLayoutConstraints( null ) { | |
| 231 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 232 | "size": new javafx.geometry.Dimension2D( 600.0, 531.0 ) | |
| 233 | } ) | |
| 234 | } | |
| 235 | } | |
| 1 | 236 |
| 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.options; | |
| 29 | ||
| 30 | import com.scrivenvar.Messages; | |
| 31 | import com.scrivenvar.Services; | |
| 32 | import com.scrivenvar.service.Options; | |
| 33 | import com.scrivenvar.service.events.impl.ButtonOrderPane; | |
| 34 | import java.util.prefs.Preferences; | |
| 35 | import javafx.event.ActionEvent; | |
| 36 | import javafx.scene.control.ButtonType; | |
| 37 | import javafx.scene.control.Dialog; | |
| 38 | import javafx.scene.control.DialogPane; | |
| 39 | import javafx.scene.control.Tab; | |
| 40 | import javafx.scene.control.TabPane; | |
| 41 | import javafx.stage.Window; | |
| 42 | ||
| 43 | /** | |
| 44 | * Options dialog. | |
| 45 | * | |
| 46 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 47 | */ | |
| 48 | public class OptionsDialog extends Dialog<Void> { | |
| 49 | ||
| 50 | private final Options options = Services.load( Options.class ); | |
| 51 | ||
| 52 | public OptionsDialog( Window owner ) { | |
| 53 | setTitle( Messages.get( "OptionsDialog.title" ) ); | |
| 54 | initOwner( owner ); | |
| 55 | ||
| 56 | initComponents(); | |
| 57 | ||
| 58 | tabPane.getStyleClass().add( TabPane.STYLE_CLASS_FLOATING ); | |
| 59 | ||
| 60 | setDialogPane( new ButtonOrderPane() ); | |
| 61 | ||
| 62 | final DialogPane dialogPane = getDialogPane(); | |
| 63 | dialogPane.setContent( tabPane ); | |
| 64 | dialogPane.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL ); | |
| 65 | ||
| 66 | dialogPane.lookupButton( ButtonType.OK ).addEventHandler( ActionEvent.ACTION, e -> { | |
| 67 | save(); | |
| 68 | e.consume(); | |
| 69 | } ); | |
| 70 | ||
| 71 | // load options | |
| 72 | load(); | |
| 73 | ||
| 74 | // select last tab | |
| 75 | int tabIndex = getState().getInt( "lastOptionsTab", -1 ); | |
| 76 | if( tabIndex > 0 ) { | |
| 77 | tabPane.getSelectionModel().select( tabIndex ); | |
| 78 | } | |
| 79 | ||
| 80 | // remember last selected tab | |
| 81 | setOnHidden( e -> { | |
| 82 | getState().putInt( "lastOptionsTab", tabPane.getSelectionModel().getSelectedIndex() ); | |
| 83 | } ); | |
| 84 | } | |
| 85 | ||
| 86 | private Options getOptions() { | |
| 87 | return options; | |
| 88 | } | |
| 89 | ||
| 90 | private Preferences getState() { | |
| 91 | return getOptions().getState(); | |
| 92 | } | |
| 93 | ||
| 94 | private void load() { | |
| 95 | generalOptionsPane.load(); | |
| 96 | } | |
| 97 | ||
| 98 | private void save() { | |
| 99 | generalOptionsPane.save(); | |
| 100 | Services.load( Options.class ).save(); | |
| 101 | } | |
| 102 | ||
| 103 | private void initComponents() { | |
| 104 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 105 | tabPane = new TabPane(); | |
| 106 | generalTab = new Tab(); | |
| 107 | generalOptionsPane = new GeneralOptionsPane(); | |
| 108 | ||
| 109 | //======== tabPane ======== | |
| 110 | { | |
| 111 | tabPane.setTabClosingPolicy( TabPane.TabClosingPolicy.UNAVAILABLE ); | |
| 112 | ||
| 113 | //======== generalTab ======== | |
| 114 | { | |
| 115 | generalTab.setText( Messages.get( "OptionsDialog.generalTab.text" ) ); | |
| 116 | generalTab.setContent( generalOptionsPane ); | |
| 117 | } | |
| 118 | ||
| 119 | tabPane.getTabs().addAll( generalTab ); | |
| 120 | } | |
| 121 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 122 | } | |
| 123 | ||
| 124 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 125 | private TabPane tabPane; | |
| 126 | private Tab generalTab; | |
| 127 | private GeneralOptionsPane generalOptionsPane; | |
| 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.keyPrefix": "OptionsDialog" | |
| 7 | "i18n.autoExternalize": true | |
| 8 | contentType: "form/javafx" | |
| 9 | root: new FormRoot { | |
| 10 | add( new FormContainer( "javafx.scene.control.TabPane", new FormLayoutManager( class javafx.scene.control.TabPane ) ) { | |
| 11 | name: "tabPane" | |
| 12 | "tabClosingPolicy": enum javafx.scene.control.TabPane$TabClosingPolicy UNAVAILABLE | |
| 13 | add( new FormContainer( "javafx.scene.control.Tab", new FormLayoutManager( class javafx.scene.control.Tab ) ) { | |
| 14 | name: "generalTab" | |
| 15 | "text": new FormMessage( null, "OptionsDialog.generalTab.text" ) | |
| 16 | add( new FormComponent( "com.scrivendor.options.GeneralOptionsPane" ) { | |
| 17 | name: "generalOptionsPane" | |
| 18 | } ) | |
| 19 | } ) | |
| 20 | add( new FormContainer( "javafx.scene.control.Tab", new FormLayoutManager( class javafx.scene.control.Tab ) ) { | |
| 21 | name: "markdownTab" | |
| 22 | "text": new FormMessage( null, "OptionsDialog.markdownTab.text" ) | |
| 23 | add( new FormComponent( "com.scrivendor.options.MarkdownOptionsPane" ) { | |
| 24 | name: "markdownOptionsPane" | |
| 25 | } ) | |
| 26 | } ) | |
| 27 | }, new FormLayoutConstraints( null ) { | |
| 28 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 29 | "size": new javafx.geometry.Dimension2D( 600.0, 560.0 ) | |
| 30 | } ) | |
| 31 | } | |
| 32 | } | |
| 1 | 33 |
| 1 | /* | |
| 2 | * Copyright 2016 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.files; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.nio.file.FileSystems; | |
| 32 | import java.nio.file.PathMatcher; | |
| 33 | import java.util.Collection; | |
| 34 | import java.util.function.Predicate; | |
| 35 | ||
| 36 | /** | |
| 37 | * Responsible for testing whether a given path (to a file) matches one of the | |
| 38 | * filename extension patterns provided during construction. | |
| 39 | * | |
| 40 | * @see http://docs.oracle.com/javase/tutorial/essential/io/find.html | |
| 41 | * | |
| 42 | * @author White Magic Software, Ltd. | |
| 43 | */ | |
| 44 | public class FileTypePredicate implements Predicate<File> { | |
| 45 | ||
| 46 | private final PathMatcher matcher; | |
| 47 | ||
| 48 | /** | |
| 49 | * Constructs a new instance given a set of file extension globs. | |
| 50 | * | |
| 51 | * @param patterns Comma-separated list of globbed extensions including the | |
| 52 | * Kleene star (e.g., <code>*.md,*.markdown,*.txt</code>). | |
| 53 | */ | |
| 54 | public FileTypePredicate( final String patterns ) { | |
| 55 | this.matcher = FileSystems.getDefault().getPathMatcher( | |
| 56 | "glob:**/{" + patterns + "}" | |
| 57 | ); | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Constructs a new instance given a list of file extension globs, each must | |
| 62 | * include the Kleene star (a.k.a. asterisk). | |
| 63 | * | |
| 64 | * @param patterns Collection of globbed extensions. | |
| 65 | */ | |
| 66 | public FileTypePredicate( final Collection<String> patterns ) { | |
| 67 | this( String.join( ",", patterns ) ); | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Returns true if the file matches the patterns defined during construction. | |
| 72 | * | |
| 73 | * @param file The filename to match against the given glob patterns. | |
| 74 | * | |
| 75 | * @return false The filename does not match the glob patterns. | |
| 76 | */ | |
| 77 | @Override | |
| 78 | public boolean test( final File file ) { | |
| 79 | return getMatcher().matches( file.toPath() ); | |
| 80 | } | |
| 81 | ||
| 82 | private PathMatcher getMatcher() { | |
| 83 | return this.matcher; | |
| 84 | } | |
| 85 | } | |
| 1 | 86 |
| 1 | /* | |
| 2 | * Copyright 2016 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.strings; | |
| 29 | ||
| 30 | /** | |
| 31 | * Determines if one string contains another. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class ContainsPredicate extends StringPredicate { | |
| 36 | ||
| 37 | /** | |
| 38 | * Calls the superclass to construct the instance. | |
| 39 | * | |
| 40 | * @param comparate Not null. | |
| 41 | */ | |
| 42 | public ContainsPredicate( final String comparate ) { | |
| 43 | super( comparate ); | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Answers whether the given strings match each other. What match means will | |
| 48 | * depend on user preferences. The empty condition is required to return the | |
| 49 | * first node in a list of child nodes when the user has not yet selected a | |
| 50 | * node. | |
| 51 | * | |
| 52 | * @param comparator The string to compare against the comparate. | |
| 53 | * | |
| 54 | * @return true if s1 and s2 are a match according to some criteria,or s2 is | |
| 55 | * empty. | |
| 56 | */ | |
| 57 | @Override | |
| 58 | public boolean test( final String comparator ) { | |
| 59 | final String comparate = getComparate().toLowerCase(); | |
| 60 | return comparator.contains( comparate.toLowerCase() ) | |
| 61 | || comparate.isEmpty(); | |
| 62 | } | |
| 63 | } | |
| 1 | 64 |
| 1 | /* | |
| 2 | * Copyright 2016 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.strings; | |
| 29 | ||
| 30 | /** | |
| 31 | * Determines if two strings are equal. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class EqualPredicate extends StringPredicate { | |
| 36 | ||
| 37 | /** | |
| 38 | * Calls the superclass to construct the instance. | |
| 39 | * | |
| 40 | * @param comparate Not null. | |
| 41 | */ | |
| 42 | public EqualPredicate( final String comparate ) { | |
| 43 | super( comparate ); | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Compares two strings. | |
| 48 | * | |
| 49 | * @param comparator A non-null string, possibly empty. | |
| 50 | * | |
| 51 | * @return true The strings are equal, ignoring case. | |
| 52 | */ | |
| 53 | @Override | |
| 54 | public boolean test( final String comparator ) { | |
| 55 | return comparator.equalsIgnoreCase( getComparate() ); | |
| 56 | } | |
| 57 | } | |
| 1 | 58 |
| 1 | /* | |
| 2 | * Copyright 2016 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.strings; | |
| 29 | ||
| 30 | /** | |
| 31 | * Determines if a string starts with another. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public class StartsPredicate extends StringPredicate { | |
| 36 | ||
| 37 | /** | |
| 38 | * Calls the superclass to construct the instance. | |
| 39 | * | |
| 40 | * @param comparate Not null. | |
| 41 | */ | |
| 42 | public StartsPredicate( final String comparate ) { | |
| 43 | super( comparate ); | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Compares two strings. | |
| 48 | * | |
| 49 | * @param comparator A non-null string, possibly empty. | |
| 50 | * | |
| 51 | * @return true The strings are equal, ignoring case. | |
| 52 | */ | |
| 53 | @Override | |
| 54 | public boolean test( final String comparator ) { | |
| 55 | final String comparate = getComparate().toLowerCase(); | |
| 56 | return comparator.startsWith( comparate.toLowerCase() ); | |
| 57 | } | |
| 58 | } | |
| 1 | 59 |
| 1 | /* | |
| 2 | * Copyright 2016 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.strings; | |
| 29 | ||
| 30 | import java.util.function.Predicate; | |
| 31 | ||
| 32 | /** | |
| 33 | * General predicate for different types of string comparisons. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public abstract class StringPredicate implements Predicate<String> { | |
| 38 | ||
| 39 | private final String comparate; | |
| 40 | ||
| 41 | public StringPredicate( final String comparate ) { | |
| 42 | this.comparate = comparate; | |
| 43 | } | |
| 44 | ||
| 45 | protected String getComparate() { | |
| 46 | return this.comparate; | |
| 47 | } | |
| 48 | } | |
| 1 | 49 |
| 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.preview; | |
| 29 | ||
| 30 | import static com.scrivenvar.Constants.CARET_POSITION; | |
| 31 | import java.nio.file.Path; | |
| 32 | import javafx.beans.value.ObservableValue; | |
| 33 | import javafx.concurrent.Worker.State; | |
| 34 | import static javafx.concurrent.Worker.State.SUCCEEDED; | |
| 35 | import javafx.scene.layout.Pane; | |
| 36 | import javafx.scene.web.WebEngine; | |
| 37 | import javafx.scene.web.WebView; | |
| 38 | ||
| 39 | /** | |
| 40 | * HTML preview pane is responsible for rendering an HTML document. | |
| 41 | * | |
| 42 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 43 | */ | |
| 44 | public final class HTMLPreviewPane extends Pane { | |
| 45 | ||
| 46 | private final WebView webView = new WebView(); | |
| 47 | private String html; | |
| 48 | private Path path; | |
| 49 | ||
| 50 | /** | |
| 51 | * Creates a new preview pane that can scroll to the caret position within the | |
| 52 | * document. | |
| 53 | * | |
| 54 | * @param path The base path for loading resources, such as images. | |
| 55 | */ | |
| 56 | public HTMLPreviewPane( final Path path ) { | |
| 57 | setPath( path ); | |
| 58 | initListeners(); | |
| 59 | ||
| 60 | // Prevent tabbing into the preview pane. | |
| 61 | getWebView().setFocusTraversable( false ); | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Updates the internal HTML source, loads it into the preview pane, then | |
| 66 | * scrolls to the caret position. | |
| 67 | * | |
| 68 | * @param html | |
| 69 | */ | |
| 70 | public void update( final String html ) { | |
| 71 | setHtml( html ); | |
| 72 | update(); | |
| 73 | } | |
| 74 | ||
| 75 | private void update() { | |
| 76 | getEngine().loadContent( | |
| 77 | "<!DOCTYPE html>" | |
| 78 | + "<html>" | |
| 79 | + "<head>" | |
| 80 | + "<link rel='stylesheet' href='" + getClass().getResource( "webview.css" ) + "'>" | |
| 81 | + getBase() | |
| 82 | + "</head>" | |
| 83 | + "<body>" | |
| 84 | + getHtml() | |
| 85 | + "</body>" | |
| 86 | + "</html>" ); | |
| 87 | } | |
| 88 | ||
| 89 | private String getBase() { | |
| 90 | final Path basePath = getPath(); | |
| 91 | ||
| 92 | return basePath == null | |
| 93 | ? "" | |
| 94 | : ("<base href='" + basePath.getParent().toUri().toString() + "'>"); | |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * Initializes observers for document changes. When the document is reloaded | |
| 99 | * with new HTML, this triggers a scroll event that repositions the document | |
| 100 | * to the injected caret (that corresponds with the position in the text | |
| 101 | * editor). | |
| 102 | */ | |
| 103 | private void initListeners() { | |
| 104 | // Scrolls to the caret after the content has been loaded. | |
| 105 | getEngine().getLoadWorker().stateProperty().addListener( | |
| 106 | (ObservableValue<? extends State> observable, | |
| 107 | State oldValue, State newValue) -> { | |
| 108 | if( newValue == SUCCEEDED ) { | |
| 109 | scrollToCaret(); | |
| 110 | } | |
| 111 | } ); | |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * Scrolls to the caret position in the document. | |
| 116 | */ | |
| 117 | private void scrollToCaret() { | |
| 118 | execute( getScrollScript() ); | |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * Returns the JavaScript used to scroll the WebView pane. | |
| 123 | * | |
| 124 | * @return A script that tries to center the view port on the CARET POSITION. | |
| 125 | */ | |
| 126 | private String getScrollScript() { | |
| 127 | return "" | |
| 128 | + "var e = document.getElementById('" + CARET_POSITION + "');" | |
| 129 | + "if( e != null ) { " | |
| 130 | + " Element.prototype.topOffset = function () {" | |
| 131 | + " return this.offsetTop + (this.offsetParent ? this.offsetParent.topOffset() : 0);" | |
| 132 | + " };" | |
| 133 | + " window.scrollTo( 0, e.topOffset() - (window.innerHeight / 2 ) );" | |
| 134 | + "}"; | |
| 135 | } | |
| 136 | ||
| 137 | private Object execute( final String script ) { | |
| 138 | return getEngine().executeScript( script ); | |
| 139 | } | |
| 140 | ||
| 141 | private WebEngine getEngine() { | |
| 142 | return getWebView().getEngine(); | |
| 143 | } | |
| 144 | ||
| 145 | public WebView getWebView() { | |
| 146 | return this.webView; | |
| 147 | } | |
| 148 | ||
| 149 | private String getHtml() { | |
| 150 | return this.html; | |
| 151 | } | |
| 152 | ||
| 153 | private void setHtml( final String html ) { | |
| 154 | this.html = html; | |
| 155 | } | |
| 156 | ||
| 157 | private Path getPath() { | |
| 158 | return this.path; | |
| 159 | } | |
| 160 | ||
| 161 | private void setPath( final Path path ) { | |
| 162 | this.path = path; | |
| 163 | } | |
| 164 | } | |
| 1 | 165 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | * @author White Magic Software, Ltd. | |
| 37 | * @param <T> The type of object to process. | |
| 38 | */ | |
| 39 | public abstract class AbstractProcessor<T> implements Processor<T> { | |
| 40 | ||
| 41 | /** | |
| 42 | * Used while processing the entire chain; null to signify no more links. | |
| 43 | */ | |
| 44 | private final Processor<T> next; | |
| 45 | ||
| 46 | /** | |
| 47 | * Constructs a succession without a successor (i.e., next is null). | |
| 48 | */ | |
| 49 | protected AbstractProcessor() { | |
| 50 | this( null ); | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Constructs a new default handler with a given successor. | |
| 55 | * | |
| 56 | * @param successor Use null to indicate last link in the chain. | |
| 57 | */ | |
| 58 | public AbstractProcessor( final Processor<T> successor ) { | |
| 59 | this.next = successor; | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Processes links in the chain while there are successors and valid data to | |
| 64 | * process. | |
| 65 | * | |
| 66 | * @param t The object to process. | |
| 67 | */ | |
| 68 | @Override | |
| 69 | public synchronized void processChain( T t ) { | |
| 70 | Processor<T> handler = this; | |
| 71 | ||
| 72 | while( handler != null && t != null ) { | |
| 73 | t = handler.processLink( t ); | |
| 74 | handler = handler.next(); | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | @Override | |
| 79 | public Processor<T> next() { | |
| 80 | return this.next; | |
| 81 | } | |
| 82 | } | |
| 1 | 83 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | * @author White Magic Software, Ltd. | |
| 40 | */ | |
| 41 | public class HTMLPreviewProcessor extends AbstractProcessor<String> { | |
| 42 | ||
| 43 | private HTMLPreviewPane htmlPreviewPane; | |
| 44 | ||
| 45 | /** | |
| 46 | * Constructs the end of a processing chain. | |
| 47 | * | |
| 48 | * @param htmlPreviewPane The pane to update with the post-processed document. | |
| 49 | */ | |
| 50 | public HTMLPreviewProcessor( final HTMLPreviewPane htmlPreviewPane ) { | |
| 51 | super( null ); | |
| 52 | setHtmlPreviewPane( htmlPreviewPane ); | |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| 56 | * Update the preview panel using HTML from the succession chain. | |
| 57 | * | |
| 58 | * @param html The document content to render in the preview pane. The HTML | |
| 59 | * should not contain a doctype, head, or body tag, only content to render | |
| 60 | * within the body. | |
| 61 | * | |
| 62 | * @return null | |
| 63 | */ | |
| 64 | @Override | |
| 65 | public String processLink( final String html ) { | |
| 66 | getHtmlPreviewPane().update( html ); | |
| 67 | ||
| 68 | // No more processing required. | |
| 69 | return null; | |
| 70 | } | |
| 71 | ||
| 72 | private HTMLPreviewPane getHtmlPreviewPane() { | |
| 73 | return this.htmlPreviewPane; | |
| 74 | } | |
| 75 | ||
| 76 | private void setHtmlPreviewPane( final HTMLPreviewPane htmlPreviewPane ) { | |
| 77 | this.htmlPreviewPane = htmlPreviewPane; | |
| 78 | } | |
| 79 | } | |
| 1 | 80 |
| 1 | /* | |
| 2 | * Copyright 2016 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 static com.scrivenvar.Constants.MD_CARET_POSITION; | |
| 31 | import static java.lang.Character.isLetter; | |
| 32 | import org.fxmisc.richtext.model.TextEditingArea; | |
| 33 | ||
| 34 | /** | |
| 35 | * Responsible for inserting the magic CARET POSITION into the markdown so | |
| 36 | * that, upon rendering into HTML, the HTML pane can scroll to the correct | |
| 37 | * position (relative to the caret position in the editor). | |
| 38 | * | |
| 39 | * @author White Magic Software, Ltd. | |
| 40 | */ | |
| 41 | public class MarkdownCaretInsertionProcessor extends AbstractProcessor<String> { | |
| 42 | ||
| 43 | private TextEditingArea editor; | |
| 44 | ||
| 45 | /** | |
| 46 | * Constructs a processor capable of inserting a caret marker into Markdown. | |
| 47 | * | |
| 48 | * @param processor The next processor in the chain. | |
| 49 | * @param editor The editor that has a caret with a position in the text. | |
| 50 | */ | |
| 51 | public MarkdownCaretInsertionProcessor( | |
| 52 | final Processor<String> processor, final TextEditingArea editor ) { | |
| 53 | super( processor ); | |
| 54 | setEditor( editor ); | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * Changes the text to insert a "caret" at the caret position. This will | |
| 59 | * insert the unique key of Constants.MD_CARET_POSITION into the document. | |
| 60 | * | |
| 61 | * @param t The document text to process. | |
| 62 | * | |
| 63 | * @return The document text with the Markdown caret text inserted at the | |
| 64 | * caret position (given at construction time). | |
| 65 | */ | |
| 66 | @Override | |
| 67 | public String processLink( final String t ) { | |
| 68 | int offset = getCaretPosition(); | |
| 69 | final int length = t.length(); | |
| 70 | ||
| 71 | // Insert the caret at the closest non-Markdown delimiter (i.e., the | |
| 72 | // closest character from the caret position forward). | |
| 73 | while( offset < length && !isLetter( t.charAt( offset ) ) ) { | |
| 74 | offset++; | |
| 75 | } | |
| 76 | ||
| 77 | // Insert the caret position into the Markdown text, but don't interfere | |
| 78 | // with the Markdown iteself. | |
| 79 | return new StringBuilder( t ).replace( | |
| 80 | offset, offset, MD_CARET_POSITION ).toString(); | |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Returns the editor's caret position. | |
| 85 | * | |
| 86 | * @return Where the user has positioned the caret. | |
| 87 | */ | |
| 88 | private int getCaretPosition() { | |
| 89 | return getEditor().getCaretPosition(); | |
| 90 | } | |
| 91 | ||
| 92 | /** | |
| 93 | * Returns the editor that has a caret position. | |
| 94 | * | |
| 95 | * @return An editor with a caret position. | |
| 96 | */ | |
| 97 | private TextEditingArea getEditor() { | |
| 98 | return this.editor; | |
| 99 | } | |
| 100 | ||
| 101 | private void setEditor( final TextEditingArea editor ) { | |
| 102 | this.editor = editor; | |
| 103 | } | |
| 104 | } | |
| 1 | 105 |
| 1 | /* | |
| 2 | * Copyright 2016 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 static com.scrivenvar.Constants.CARET_POSITION; | |
| 31 | import static com.scrivenvar.Constants.MD_CARET_POSITION; | |
| 32 | ||
| 33 | /** | |
| 34 | * Responsible for replacing the caret position marker with an HTML element | |
| 35 | * suitable to use as a reference for scrolling a view port. | |
| 36 | * | |
| 37 | * @author White Magic Software, Ltd. | |
| 38 | */ | |
| 39 | public class MarkdownCaretReplacementProcessor extends AbstractProcessor<String> { | |
| 40 | private static final int INDEX_NOT_FOUND = -1; | |
| 41 | ||
| 42 | private static final String HTML_ELEMENT | |
| 43 | = "<span id='" + CARET_POSITION + "'></span>"; | |
| 44 | ||
| 45 | public MarkdownCaretReplacementProcessor( final Processor<String> processor ) { | |
| 46 | super( processor ); | |
| 47 | } | |
| 48 | ||
| 49 | /** | |
| 50 | * Replaces each MD_CARET_POSITION with an HTML element that has an id | |
| 51 | * attribute of CARET_POSITION. This should only replace one item. | |
| 52 | * | |
| 53 | * @param t The text that contains | |
| 54 | * | |
| 55 | * @return | |
| 56 | */ | |
| 57 | @Override | |
| 58 | public String processLink( final String t ) { | |
| 59 | return replace( t, MD_CARET_POSITION, HTML_ELEMENT ); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Replaces the needle with thread in the given haystack. Based on Apache | |
| 64 | * Commons 3 StringUtils.replace method. Should be faster than | |
| 65 | * String.replace, which performs a little regex under the hood. | |
| 66 | * | |
| 67 | * @param haystack Search this string for the needle, must not be null. | |
| 68 | * @param needle The text to find in the haystack. | |
| 69 | * @param thread Replace the needle with this text, if the needle is found. | |
| 70 | * | |
| 71 | * @return The haystack with the first instance of needle replaced with | |
| 72 | * thread. | |
| 73 | */ | |
| 74 | private static String replace( | |
| 75 | final String haystack, final String needle, final String thread ) { | |
| 76 | ||
| 77 | final int end = haystack.indexOf( needle, 0 ); | |
| 78 | ||
| 79 | if( end == INDEX_NOT_FOUND ) { | |
| 80 | return haystack; | |
| 81 | } | |
| 82 | ||
| 83 | int start = 0; | |
| 84 | final int needleLength = needle.length(); | |
| 85 | ||
| 86 | int increase = thread.length() - needleLength; | |
| 87 | increase = (increase < 0 ? 0 : increase); | |
| 88 | final StringBuilder buffer = new StringBuilder( haystack.length() + increase ); | |
| 89 | ||
| 90 | if( end != INDEX_NOT_FOUND ) { | |
| 91 | buffer.append( haystack.substring( start, end ) ).append( thread ); | |
| 92 | start = end + needleLength; | |
| 93 | } | |
| 94 | ||
| 95 | return buffer.append( haystack.substring( start ) ).toString(); | |
| 96 | } | |
| 97 | } | |
| 1 | 98 |
| 1 | /* | |
| 2 | * Copyright 2016 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.vladsch.flexmark.Extension; | |
| 31 | import com.vladsch.flexmark.ast.Node; | |
| 32 | import com.vladsch.flexmark.ext.gfm.tables.TablesExtension; | |
| 33 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 34 | import com.vladsch.flexmark.parser.Parser; | |
| 35 | import java.util.ArrayList; | |
| 36 | import java.util.List; | |
| 37 | ||
| 38 | ||
| 39 | /** | |
| 40 | * Responsible for parsing a Markdown document and rendering it as HTML. | |
| 41 | * | |
| 42 | * @author White Magic Software, Ltd. | |
| 43 | */ | |
| 44 | public class MarkdownProcessor extends AbstractProcessor<String> { | |
| 45 | ||
| 46 | private List<Extension> extensions; | |
| 47 | ||
| 48 | /** | |
| 49 | * Constructs a new Markdown processor that can create HTML documents. | |
| 50 | * | |
| 51 | * @param successor Usually the HTML Preview Processor. | |
| 52 | */ | |
| 53 | public MarkdownProcessor( final Processor<String> successor ) { | |
| 54 | super( successor ); | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * Converts the given Markdown string into HTML, without the doctype, html, | |
| 59 | * head, and body tags. | |
| 60 | * | |
| 61 | * @param markdown The string to convert from Markdown to HTML. | |
| 62 | * | |
| 63 | * @return The HTML representation of the Markdown document. | |
| 64 | */ | |
| 65 | @Override | |
| 66 | public String processLink( final String markdown ) { | |
| 67 | return toHtml( markdown ); | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Returns the AST in the form of a node for the given markdown document. This | |
| 72 | * can be used, for example, to determine if a hyperlink exists inside of a | |
| 73 | * paragraph. | |
| 74 | * | |
| 75 | * @param markdown The markdown to convert into an AST. | |
| 76 | * | |
| 77 | * @return The markdown AST for the given text (usually a paragraph). | |
| 78 | */ | |
| 79 | public Node toNode( final String markdown ) { | |
| 80 | return parse( markdown ); | |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Helper method to create an AST given some markdown. | |
| 85 | * | |
| 86 | * @param markdown The markdown to parse. | |
| 87 | * | |
| 88 | * @return The root node of the markdown tree. | |
| 89 | */ | |
| 90 | private Node parse( final String markdown ) { | |
| 91 | return createParser().parse( markdown ); | |
| 92 | } | |
| 93 | ||
| 94 | /** | |
| 95 | * Converts a string of markdown into HTML. | |
| 96 | * | |
| 97 | * @param markdown The markdown text to convert to HTML, must not be null. | |
| 98 | * | |
| 99 | * @return The markdown rendered as an HTML document. | |
| 100 | */ | |
| 101 | private String toHtml( final String markdown ) { | |
| 102 | return createRenderer().render( parse( markdown ) ); | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Returns the list of extensions to use when parsing and rendering Markdown | |
| 107 | * into HTML. | |
| 108 | * | |
| 109 | * @return A non-null list of Markdown extensions. | |
| 110 | */ | |
| 111 | private synchronized List<Extension> getExtensions() { | |
| 112 | if( this.extensions == null ) { | |
| 113 | this.extensions = createExtensions(); | |
| 114 | } | |
| 115 | ||
| 116 | return this.extensions; | |
| 117 | } | |
| 118 | ||
| 119 | /** | |
| 120 | * Creates a list that includes a TablesExtension. Subclasses may override | |
| 121 | * this method to insert more extensions, or remove the table extension. | |
| 122 | * | |
| 123 | * @return A list with an extension for parsing and rendering tables. | |
| 124 | */ | |
| 125 | protected List<Extension> createExtensions() { | |
| 126 | final List<Extension> result = new ArrayList<>(); | |
| 127 | result.add( TablesExtension.create() ); | |
| 128 | return result; | |
| 129 | } | |
| 130 | ||
| 131 | /** | |
| 132 | * Creates the Markdown document processor. | |
| 133 | * | |
| 134 | * @return A Parser that can build an abstract syntax tree. | |
| 135 | */ | |
| 136 | private Parser createParser() { | |
| 137 | return Parser.builder().extensions( getExtensions() ).build(); | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Creates the HTML document renderer. | |
| 142 | * | |
| 143 | * @return A renderer that can convert a Markdown AST to HTML. | |
| 144 | */ | |
| 145 | private HtmlRenderer createRenderer() { | |
| 146 | return HtmlRenderer.builder().extensions( getExtensions() ).build(); | |
| 147 | } | |
| 148 | } | |
| 1 | 149 |
| 1 | /* | |
| 2 | * Copyright 2016 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 processing documents from one known format to another. | |
| 32 | * | |
| 33 | * @author White Magic Software, Ltd. | |
| 34 | * @param <T> The type of processor to create. | |
| 35 | */ | |
| 36 | public interface Processor<T> { | |
| 37 | ||
| 38 | /** | |
| 39 | * Provided so that the chain can be invoked from any link using a given | |
| 40 | * value. This should be called automatically by a superclass so that | |
| 41 | * the links in the chain need only implement the processLink method. | |
| 42 | * | |
| 43 | * @param t The value to pass along to each link in the chain. | |
| 44 | * @return The value after having been processed by each link. | |
| 45 | */ | |
| 46 | public void processChain( T t ); | |
| 47 | ||
| 48 | /** | |
| 49 | * Processes the given content providing a transformation from one document | |
| 50 | * format into another. For example, this could convert from XML to text using | |
| 51 | * an XSLT processor, or from markdown to HTML. | |
| 52 | * | |
| 53 | * @param t The type of object to process. | |
| 54 | * | |
| 55 | * @return The post-processed document, or null if processing should stop. | |
| 56 | */ | |
| 57 | public T processLink( T t ); | |
| 58 | ||
| 59 | /** | |
| 60 | * Adds a document processor to call after this processor finishes processing | |
| 61 | * the document given to the process method. | |
| 62 | * | |
| 63 | * @return The processor that should transform the document after this | |
| 64 | * instance has finished processing. | |
| 65 | */ | |
| 66 | public Processor<T> next(); | |
| 67 | } | |
| 1 | 68 |
| 1 | /* | |
| 2 | * Copyright 2016 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 javafx.beans.value.ChangeListener; | |
| 31 | import javafx.beans.value.ObservableValue; | |
| 32 | ||
| 33 | /** | |
| 34 | * Responsible for forwarding change events to the document process chain. This | |
| 35 | * class isolates knowledge of the change events from the other processors. | |
| 36 | * | |
| 37 | * @author White Magic Software, Ltd. | |
| 38 | */ | |
| 39 | public class TextChangeProcessor extends AbstractProcessor<String> | |
| 40 | implements ChangeListener<String> { | |
| 41 | ||
| 42 | /** | |
| 43 | * Constructs a new text processor that listens for changes to text and then | |
| 44 | * injects them into the processing chain. | |
| 45 | * | |
| 46 | * @param successor Usually the HTML Preview Processor. | |
| 47 | */ | |
| 48 | public TextChangeProcessor( final Processor<String> successor ) { | |
| 49 | super( successor ); | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Called when the text editor changes. | |
| 54 | * | |
| 55 | * @param observable Unused. | |
| 56 | * @param oldValue The value before being changed (unused). | |
| 57 | * @param newValue The value after being changed (passed to processChain). | |
| 58 | */ | |
| 59 | @Override | |
| 60 | public void changed( | |
| 61 | final ObservableValue<? extends String> observable, | |
| 62 | final String oldValue, | |
| 63 | final String newValue ) { | |
| 64 | processChain( newValue ); | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Performs no processing. | |
| 69 | * | |
| 70 | * @param t Returned value. | |
| 71 | * | |
| 72 | * @return t, without any processing. | |
| 73 | */ | |
| 74 | @Override | |
| 75 | public String processLink( String t ) { | |
| 76 | return t; | |
| 77 | } | |
| 78 | } | |
| 1 | 79 |
| 1 | /* | |
| 2 | * Copyright 2016 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.processors.text.TextReplacementFactory; | |
| 31 | import com.scrivenvar.processors.text.TextReplacer; | |
| 32 | import java.util.Map; | |
| 33 | ||
| 34 | /** | |
| 35 | * Processes variables in the document and inserts their values into the | |
| 36 | * post-processed text. | |
| 37 | * | |
| 38 | * @author White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public class VariableProcessor extends AbstractProcessor<String> { | |
| 41 | ||
| 42 | private Map<String, String> definitions; | |
| 43 | ||
| 44 | /** | |
| 45 | * Constructs a new Markdown processor that can create HTML documents. | |
| 46 | * | |
| 47 | * @param successor Usually the HTML Preview Processor. | |
| 48 | */ | |
| 49 | private VariableProcessor( final Processor<String> successor ) { | |
| 50 | super( successor ); | |
| 51 | } | |
| 52 | ||
| 53 | public VariableProcessor( | |
| 54 | final Processor<String> successor, | |
| 55 | final Map<String, String> map ) { | |
| 56 | this( successor ); | |
| 57 | setDefinitions( map ); | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * | |
| 62 | * @param text The document text that includes variables that should be | |
| 63 | * replaced with values when rendered as HTML. | |
| 64 | * | |
| 65 | * @return The text with all variables replaced. | |
| 66 | */ | |
| 67 | @Override | |
| 68 | public String processLink( final String text ) { | |
| 69 | final TextReplacer tr = TextReplacementFactory.getTextReplacer( text.length() ); | |
| 70 | ||
| 71 | return tr.replace( text, getDefinitions() ); | |
| 72 | } | |
| 73 | ||
| 74 | private Map<String, String> getDefinitions() { | |
| 75 | return this.definitions; | |
| 76 | } | |
| 77 | ||
| 78 | private void setDefinitions( final Map<String, String> definitions ) { | |
| 79 | this.definitions = definitions; | |
| 80 | } | |
| 81 | } | |
| 1 | 82 |
| 1 | /* | |
| 2 | * The MIT License | |
| 3 | * | |
| 4 | * Copyright 2016 . | |
| 5 | * | |
| 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 7 | * of this software and associated documentation files (the "Software"), to deal | |
| 8 | * in the Software without restriction, including without limitation the rights | |
| 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 10 | * copies of the Software, and to permit persons to whom the Software is | |
| 11 | * furnished to do so, subject to the following conditions: | |
| 12 | * | |
| 13 | * The above copyright notice and this permission notice shall be included in | |
| 14 | * all copies or substantial portions of the Software. | |
| 15 | * | |
| 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 22 | * THE SOFTWARE. | |
| 23 | */ | |
| 24 | package com.scrivenvar.processors.text; | |
| 25 | ||
| 26 | import java.util.Map; | |
| 27 | ||
| 28 | /** | |
| 29 | * Responsible for common behaviour across all text replacer implementations. | |
| 30 | * | |
| 31 | * @author White Magic Software, Ltd. | |
| 32 | */ | |
| 33 | public abstract class AbstractTextReplacer implements TextReplacer { | |
| 34 | ||
| 35 | /** | |
| 36 | * Default (empty) constructor. | |
| 37 | */ | |
| 38 | protected AbstractTextReplacer() { | |
| 39 | } | |
| 40 | ||
| 41 | protected String[] keys( final Map<String, String> map ) { | |
| 42 | return map.keySet().toArray( new String[ map.size() ] ); | |
| 43 | } | |
| 44 | ||
| 45 | protected String[] values( final Map<String, String> map ) { | |
| 46 | return map.values().toArray( new String[ map.size() ] ); | |
| 47 | } | |
| 48 | } | |
| 1 | 49 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | * @author White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public class AhoCorasickReplacer extends AbstractTextReplacer { | |
| 41 | ||
| 42 | /** | |
| 43 | * Default (empty) constructor. | |
| 44 | */ | |
| 45 | protected AhoCorasickReplacer() { | |
| 46 | } | |
| 47 | ||
| 48 | @Override | |
| 49 | public String replace( final String text, final Map<String, String> map ) { | |
| 50 | // Create a buffer sufficiently large that re-allocations are minimized. | |
| 51 | final StringBuilder sb = new StringBuilder( (int)(text.length() * 1.25) ); | |
| 52 | ||
| 53 | // The TrieBuilder should only match whole words and ignore overlaps (there | |
| 54 | // shouldn't be any). | |
| 55 | final TrieBuilder builder = builder().onlyWholeWords().removeOverlaps(); | |
| 56 | ||
| 57 | for( final String key : keys( map ) ) { | |
| 58 | builder.addKeyword( key ); | |
| 59 | } | |
| 60 | ||
| 61 | int index = 0; | |
| 62 | ||
| 63 | for( final Emit emit : builder.build().parseText( text ) ) { | |
| 64 | sb.append( text.substring( index, emit.getStart() ) ); | |
| 65 | sb.append( map.get( emit.getKeyword() ) ); | |
| 66 | index = emit.getEnd() + 1; | |
| 67 | } | |
| 68 | ||
| 69 | // Add the remainder of the string (contains no more matches). | |
| 70 | sb.append( text.substring( index ) ); | |
| 71 | ||
| 72 | return sb.toString(); | |
| 73 | } | |
| 74 | } | |
| 1 | 75 |
| 1 | /* | |
| 2 | * Copyright 2016 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 static org.apache.commons.lang.StringUtils.replaceEach; | |
| 32 | ||
| 33 | /** | |
| 34 | * Replaces text using Apache's StringUtils.replaceEach method. | |
| 35 | * | |
| 36 | * @author White Magic Software, Ltd. | |
| 37 | */ | |
| 38 | public class StringUtilsReplacer extends AbstractTextReplacer { | |
| 39 | ||
| 40 | /** | |
| 41 | * Default (empty) constructor. | |
| 42 | */ | |
| 43 | protected StringUtilsReplacer() { | |
| 44 | } | |
| 45 | ||
| 46 | @Override | |
| 47 | public String replace( final String text, final Map<String, String> map ) { | |
| 48 | return replaceEach( text, keys( map ), values( map ) ); | |
| 49 | } | |
| 50 | } | |
| 1 | 51 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | /** | |
| 31 | * Used to generate a class capable of efficiently replacing variable | |
| 32 | * definitions with their values. | |
| 33 | * | |
| 34 | * @author White Magic Software, Ltd. | |
| 35 | */ | |
| 36 | public class TextReplacementFactory { | |
| 37 | ||
| 38 | /** | |
| 39 | * Returns a text search/replacement instance that is reasonably optimal for | |
| 40 | * the given length of text. | |
| 41 | * | |
| 42 | * @param length The length of text that requires some search and replacing. | |
| 43 | * | |
| 44 | * @return A class that can search and replace text with utmost expediency. | |
| 45 | */ | |
| 46 | public static TextReplacer getTextReplacer( final int length ) { | |
| 47 | // After about 1,500 characters, the StringUtils implementation is less | |
| 48 | // performant than the Aho-Corsick implementation. | |
| 49 | // | |
| 50 | // Ssee http://stackoverflow.com/a/40836618/59087 | |
| 51 | return length < 1500 | |
| 52 | ? new StringUtilsReplacer() | |
| 53 | : new AhoCorasickReplacer(); | |
| 54 | } | |
| 55 | } | |
| 1 | 56 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public interface TextReplacer { | |
| 38 | ||
| 39 | /** | |
| 40 | * Searches through the given text for any of the keys given in the map and | |
| 41 | * replaces the keys that appear in the text with the key's corresponding | |
| 42 | * value. | |
| 43 | * | |
| 44 | * @param text The text that contains zero or more keys. | |
| 45 | * @param map The set of keys mapped to replacement values. | |
| 46 | * | |
| 47 | * @return The given text with all keys replaced with corresponding values. | |
| 48 | */ | |
| 49 | public String replace( String text, Map<String, String> map ); | |
| 50 | } | |
| 1 | 51 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | * | |
| 32 | * @author White Magic Software, Ltd. | |
| 33 | */ | |
| 34 | public interface Configuration extends Service { | |
| 35 | ||
| 36 | public Settings getSettings(); | |
| 37 | ||
| 38 | public Options getOptions(); | |
| 39 | } | |
| 1 | 40 |
| 1 | /* | |
| 2 | * Copyright 2016 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.prefs.Preferences; | |
| 31 | import javafx.beans.property.StringProperty; | |
| 32 | ||
| 33 | /** | |
| 34 | * Options | |
| 35 | * | |
| 36 | * @author White Magic Software, Ltd. | |
| 37 | */ | |
| 38 | public interface Options { | |
| 39 | ||
| 40 | public Preferences getState(); | |
| 41 | ||
| 42 | public void load( Preferences options ); | |
| 43 | ||
| 44 | public void save(); | |
| 45 | ||
| 46 | public String getLineSeparator(); | |
| 47 | ||
| 48 | public void setLineSeparator( String lineSeparator ); | |
| 49 | ||
| 50 | public StringProperty lineSeparatorProperty(); | |
| 51 | ||
| 52 | public String getEncoding(); | |
| 53 | ||
| 54 | public void setEncoding( String encoding ); | |
| 55 | ||
| 56 | public StringProperty encodingProperty(); | |
| 57 | } | |
| 1 | 58 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | * @author White Magic Software, Ltd. | |
| 34 | */ | |
| 35 | public interface Service { | |
| 36 | } | |
| 1 | 37 |
| 1 | /* | |
| 2 | * Copyright 2016 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.List; | |
| 31 | ||
| 32 | /** | |
| 33 | * Defines how settings and options can be retrieved. | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public interface Settings extends Service { | |
| 38 | ||
| 39 | /** | |
| 40 | * Returns a setting property or its default value. | |
| 41 | * | |
| 42 | * @param property The property key name to obtain its value. | |
| 43 | * @param defaultValue The default value to return iff the property cannot | |
| 44 | * be found. | |
| 45 | * | |
| 46 | * @return The property value for the given property key. | |
| 47 | */ | |
| 48 | public String getSetting( String property, String defaultValue ); | |
| 49 | ||
| 50 | /** | |
| 51 | * Returns a setting property or its default value. | |
| 52 | * | |
| 53 | * @param property The property key name to obtain its value. | |
| 54 | * @param defaultValue The default value to return iff the property cannot | |
| 55 | * be found. | |
| 56 | * | |
| 57 | * @return The property value for the given property key. | |
| 58 | */ | |
| 59 | public int getSetting( String property, int defaultValue ); | |
| 60 | ||
| 61 | /** | |
| 62 | * Returns a setting property or its default value. | |
| 63 | * | |
| 64 | * @param property The property key name to obtain its value. | |
| 65 | * @param defaults The default values to return iff the property cannot | |
| 66 | * be found. | |
| 67 | * | |
| 68 | * @return The property values for the given property key. | |
| 69 | */ | |
| 70 | public List<Object> getSettingList( String property, List<String> defaults ); | |
| 71 | ||
| 72 | ||
| 73 | /** | |
| 74 | * Convert the generic list of property objects into strings. | |
| 75 | * | |
| 76 | * @param property The property value to coerce. | |
| 77 | * @param defaults The defaults values to use should the property be unset. | |
| 78 | * | |
| 79 | * @return The list of properties coerced from objects to strings. | |
| 80 | */ | |
| 81 | public List<String> getStringSettingList( String property, List<String> defaults ); | |
| 82 | } | |
| 1 | 83 |
| 1 | /* | |
| 2 | * Copyright 2016 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 | * | |
| 32 | * @author White Magic Software, Ltd. | |
| 33 | */ | |
| 34 | public interface AlertMessage { | |
| 35 | ||
| 36 | /** | |
| 37 | * Dialog box title. | |
| 38 | * | |
| 39 | * @return A non-null string to use as the title for the dialog. | |
| 40 | */ | |
| 41 | public String getTitle(); | |
| 42 | ||
| 43 | /** | |
| 44 | * Dialog box message content. | |
| 45 | * | |
| 46 | * @return A non-null string to use as the alert message for the dialog. | |
| 47 | */ | |
| 48 | public String getContent(); | |
| 49 | } | |
| 1 | 50 |
| 1 | /* | |
| 2 | * Copyright 2016 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 create alert dialogs. | |
| 36 | * | |
| 37 | * @author White Magic Software, Ltd. | |
| 38 | */ | |
| 39 | public interface AlertService { | |
| 40 | public static final ButtonType YES = ButtonType.YES; | |
| 41 | public static final ButtonType NO = ButtonType.NO; | |
| 42 | public static final ButtonType CANCEL = ButtonType.CANCEL; | |
| 43 | ||
| 44 | /** | |
| 45 | * Called to set the window used as the parent for the alert dialogs. | |
| 46 | * | |
| 47 | * @param window | |
| 48 | */ | |
| 49 | public void setWindow( Window window ); | |
| 50 | ||
| 51 | /** | |
| 52 | * Constructs a default alert message text for a modal alert dialog. | |
| 53 | * | |
| 54 | * @param title The dialog box message title. | |
| 55 | * @param message The dialog box message content (needs formatting). | |
| 56 | * @param args The arguments to the message content that must be formatted. | |
| 57 | * | |
| 58 | * @return The message suitable for building a modal alert dialog. | |
| 59 | */ | |
| 60 | public AlertMessage createAlertMessage( | |
| 61 | String title, | |
| 62 | String message, | |
| 63 | Object... args ); | |
| 64 | ||
| 65 | /** | |
| 66 | * Creates an alert of alert type error with a message showing the cause of | |
| 67 | * the error. | |
| 68 | * | |
| 69 | * @param alertMessage The error message, title, and possibly more details. | |
| 70 | * | |
| 71 | * @return A modal alert dialog box ready to display using showAndWait. | |
| 72 | */ | |
| 73 | public Alert createAlertError( AlertMessage alertMessage ); | |
| 74 | ||
| 75 | /** | |
| 76 | * Creates an alert of alert type confirmation with Yes/No/Cancel buttons. | |
| 77 | * | |
| 78 | * @param alertMessage The message, title, and possibly more details. | |
| 79 | * | |
| 80 | * @return A modal alert dialog box ready to display using showAndWait. | |
| 81 | */ | |
| 82 | public Alert createAlertConfirmation( AlertMessage alertMessage ); | |
| 83 | } | |
| 1 | 84 |
| 1 | /* | |
| 2 | * Copyright 2016 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.Services; | |
| 31 | import com.scrivenvar.service.Settings; | |
| 32 | import javafx.scene.Node; | |
| 33 | import javafx.scene.control.ButtonBar; | |
| 34 | import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS; | |
| 35 | import javafx.scene.control.DialogPane; | |
| 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 | * @author White Magic Software, Ltd. | |
| 42 | */ | |
| 43 | public class ButtonOrderPane extends DialogPane { | |
| 44 | ||
| 45 | @Override | |
| 46 | protected Node createButtonBar() { | |
| 47 | final ButtonBar node = (ButtonBar)super.createButtonBar(); | |
| 48 | node.setButtonOrder( getButtonOrder() ); | |
| 49 | return node; | |
| 50 | } | |
| 51 | ||
| 52 | private String getButtonOrder() { | |
| 53 | return getSetting( "dialog.alert.button.order.windows", BUTTON_ORDER_WINDOWS ); | |
| 54 | } | |
| 55 | ||
| 56 | private String getSetting( final String key, final String defaultValue ) { | |
| 57 | return getSettings().getSetting( key, defaultValue ); | |
| 58 | } | |
| 59 | ||
| 60 | private Settings getSettings() { | |
| 61 | return Services.load( Settings.class ); | |
| 62 | } | |
| 63 | } | |
| 1 | 64 |
| 1 | /* | |
| 2 | * Copyright 2016 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.AlertMessage; | |
| 31 | import java.text.MessageFormat; | |
| 32 | ||
| 33 | /** | |
| 34 | * | |
| 35 | * @author White Magic Software, Ltd. | |
| 36 | */ | |
| 37 | public class DefaultAlertMessage implements AlertMessage { | |
| 38 | ||
| 39 | private final String title; | |
| 40 | private final String content; | |
| 41 | ||
| 42 | /** | |
| 43 | * Constructs a default alert message text for an alert modal dialog. | |
| 44 | * | |
| 45 | * @param title The dialog box message title. | |
| 46 | * @param message The dialog box message content (needs formatting). | |
| 47 | * @param args The arguments to the message content that must be formatted. | |
| 48 | */ | |
| 49 | public DefaultAlertMessage( | |
| 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 2016 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.AlertMessage; | |
| 31 | import com.scrivenvar.service.events.AlertService; | |
| 32 | import javafx.scene.control.Alert; | |
| 33 | import javafx.scene.control.Alert.AlertType; | |
| 34 | import static javafx.scene.control.Alert.AlertType.CONFIRMATION; | |
| 35 | import static javafx.scene.control.Alert.AlertType.ERROR; | |
| 36 | import javafx.stage.Window; | |
| 37 | ||
| 38 | /** | |
| 39 | * Provides the ability to create error alert boxes. | |
| 40 | * | |
| 41 | * @author White Magic Software, Ltd. | |
| 42 | */ | |
| 43 | public final class DefaultAlertService implements AlertService { | |
| 44 | ||
| 45 | private Window window; | |
| 46 | ||
| 47 | public DefaultAlertService() { | |
| 48 | } | |
| 49 | ||
| 50 | public DefaultAlertService( final Window window ) { | |
| 51 | this.window = window; | |
| 52 | } | |
| 53 | ||
| 54 | @Override | |
| 55 | public AlertMessage createAlertMessage( | |
| 56 | final String title, | |
| 57 | final String message, | |
| 58 | final Object... args ) { | |
| 59 | return new DefaultAlertMessage( title, message, args ); | |
| 60 | } | |
| 61 | ||
| 62 | private Alert createAlertDialog( | |
| 63 | final AlertType alertType, | |
| 64 | final AlertMessage 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( getWindow() ); | |
| 73 | ||
| 74 | return alert; | |
| 75 | } | |
| 76 | ||
| 77 | @Override | |
| 78 | public Alert createAlertConfirmation( final AlertMessage message ) { | |
| 79 | final Alert alert = createAlertDialog( CONFIRMATION, message ); | |
| 80 | ||
| 81 | alert.getButtonTypes().setAll( YES, NO, CANCEL ); | |
| 82 | ||
| 83 | return alert; | |
| 84 | } | |
| 85 | ||
| 86 | @Override | |
| 87 | public Alert createAlertError( final AlertMessage message ) { | |
| 88 | return createAlertDialog( ERROR, message ); | |
| 89 | } | |
| 90 | ||
| 91 | private Window getWindow() { | |
| 92 | return this.window; | |
| 93 | } | |
| 94 | ||
| 95 | @Override | |
| 96 | public void setWindow( Window window ) { | |
| 97 | this.window = window; | |
| 98 | } | |
| 99 | } | |
| 1 | 100 |
| 1 | /* | |
| 2 | * To change this license header, choose License Headers in Project Properties. | |
| 3 | * To change this template file, choose Tools | Templates | |
| 4 | * and open the template in the editor. | |
| 5 | */ | |
| 6 | package com.scrivenvar.service.events.impl; | |
| 7 | ||
| 8 | /** | |
| 9 | * Lists known file types for creating document processors via the factory. | |
| 10 | * | |
| 11 | * @author White Magic Software, Ltd. | |
| 12 | */ | |
| 13 | public enum FileType { | |
| 14 | MARKDOWN("md", "markdown", "mkdown", "mdown", "mkdn", "mkd", "mdwn", "mdtxt", "mdtext", "text", "txt"), | |
| 15 | R_MARKDOWN("Rmd"), | |
| 16 | XML("xml"); | |
| 17 | ||
| 18 | private final String[] extensions; | |
| 19 | ||
| 20 | private FileType(final String... extensions) { | |
| 21 | this.extensions = extensions; | |
| 22 | } | |
| 23 | ||
| 24 | /** | |
| 25 | * Returns true if the given file type aligns with the extension for this | |
| 26 | * enumeration. | |
| 27 | * | |
| 28 | * @param filetype The file extension to compare against the internal list. | |
| 29 | * @return true The given filetype equals (case insensitive) the internal | |
| 30 | * type. | |
| 31 | */ | |
| 32 | public boolean isType(final String filetype) { | |
| 33 | boolean result = false; | |
| 34 | ||
| 35 | for (final String extension : this.extensions) { | |
| 36 | if (extension.equalsIgnoreCase(filetype)) { | |
| 37 | result = true; | |
| 38 | break; | |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | return result; | |
| 43 | } | |
| 44 | } | |
| 1 | 45 |
| 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 | package com.scrivenvar.service.impl; | |
| 28 | ||
| 29 | import com.scrivenvar.service.Options; | |
| 30 | import static com.scrivenvar.util.Utils.putPrefs; | |
| 31 | import java.util.prefs.Preferences; | |
| 32 | import static java.util.prefs.Preferences.userRoot; | |
| 33 | import javafx.beans.property.SimpleStringProperty; | |
| 34 | import javafx.beans.property.StringProperty; | |
| 35 | ||
| 36 | /** | |
| 37 | * Persistent options user can change at runtime. | |
| 38 | * | |
| 39 | * @author Karl Tauber and White Magic Software, Ltd. | |
| 40 | */ | |
| 41 | public class DefaultOptions implements Options { | |
| 42 | private final StringProperty LINE_SEPARATOR = new SimpleStringProperty(); | |
| 43 | private final StringProperty ENCODING = new SimpleStringProperty(); | |
| 44 | ||
| 45 | private Preferences preferences; | |
| 46 | ||
| 47 | public DefaultOptions() { | |
| 48 | setPreferences( getRootPreferences().node( "options" ) ); | |
| 49 | } | |
| 50 | ||
| 51 | private void setPreferences( Preferences preferences ) { | |
| 52 | this.preferences = preferences; | |
| 53 | } | |
| 54 | ||
| 55 | private Preferences getRootPreferences() { | |
| 56 | return userRoot().node( "application" ); | |
| 57 | } | |
| 58 | ||
| 59 | @Override | |
| 60 | public Preferences getState() { | |
| 61 | return getRootPreferences().node( "state" ); | |
| 62 | } | |
| 63 | ||
| 64 | public Preferences getPreferences() { | |
| 65 | return this.preferences; | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | public void load( Preferences options ) { | |
| 70 | setLineSeparator( options.get( "lineSeparator", null ) ); | |
| 71 | setEncoding( options.get( "encoding", null ) ); | |
| 72 | } | |
| 73 | ||
| 74 | @Override | |
| 75 | public void save() { | |
| 76 | final Preferences prefs = getPreferences(); | |
| 77 | ||
| 78 | putPrefs( prefs, "lineSeparator", getLineSeparator(), null ); | |
| 79 | putPrefs( prefs, "encoding", getEncoding(), null ); | |
| 80 | } | |
| 81 | ||
| 82 | @Override | |
| 83 | public String getLineSeparator() { | |
| 84 | return LINE_SEPARATOR.get(); | |
| 85 | } | |
| 86 | ||
| 87 | @Override | |
| 88 | public void setLineSeparator( String lineSeparator ) { | |
| 89 | LINE_SEPARATOR.set( lineSeparator ); | |
| 90 | } | |
| 91 | ||
| 92 | @Override | |
| 93 | public StringProperty lineSeparatorProperty() { | |
| 94 | return LINE_SEPARATOR; | |
| 95 | } | |
| 96 | ||
| 97 | @Override | |
| 98 | public String getEncoding() { | |
| 99 | return ENCODING.get(); | |
| 100 | } | |
| 101 | ||
| 102 | @Override | |
| 103 | public void setEncoding( String encoding ) { | |
| 104 | ENCODING.set( encoding ); | |
| 105 | } | |
| 106 | ||
| 107 | @Override | |
| 108 | public StringProperty encodingProperty() { | |
| 109 | return ENCODING; | |
| 110 | } | |
| 111 | } | |
| 1 | 112 |
| 1 | /* | |
| 2 | * Copyright 2016 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 static com.scrivenvar.Constants.SETTINGS_NAME; | |
| 31 | import com.scrivenvar.service.Settings; | |
| 32 | import java.io.IOException; | |
| 33 | import java.net.URISyntaxException; | |
| 34 | import java.net.URL; | |
| 35 | import java.util.ArrayList; | |
| 36 | import java.util.List; | |
| 37 | import java.util.Objects; | |
| 38 | import java.util.stream.Collectors; | |
| 39 | import org.apache.commons.configuration.ConfigurationException; | |
| 40 | import org.apache.commons.configuration.PropertiesConfiguration; | |
| 41 | ||
| 42 | /** | |
| 43 | * Responsible for loading settings that help avoid hard-coded assumptions. | |
| 44 | * | |
| 45 | * @author White Magic Software, Ltd. | |
| 46 | */ | |
| 47 | public class DefaultSettings implements Settings { | |
| 48 | ||
| 49 | private PropertiesConfiguration properties; | |
| 50 | ||
| 51 | public DefaultSettings() | |
| 52 | throws ConfigurationException, URISyntaxException, IOException { | |
| 53 | setProperties(createProperties()); | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Returns the value of a string property. | |
| 58 | * | |
| 59 | * @param property The property key. | |
| 60 | * @param defaultValue The value to return if no property key has been set. | |
| 61 | * | |
| 62 | * @return The property key value, or defaultValue when no key found. | |
| 63 | */ | |
| 64 | @Override | |
| 65 | public String getSetting(final String property, final String defaultValue) { | |
| 66 | return getSettings().getString(property, defaultValue); | |
| 67 | } | |
| 68 | ||
| 69 | /** | |
| 70 | * Returns the value of a string property. | |
| 71 | * | |
| 72 | * @param property The property key. | |
| 73 | * @param defaultValue The value to return if no property key has been set. | |
| 74 | * | |
| 75 | * @return The property key value, or defaultValue when no key found. | |
| 76 | */ | |
| 77 | @Override | |
| 78 | public int getSetting(final String property, final int defaultValue) { | |
| 79 | return getSettings().getInt(property, defaultValue); | |
| 80 | } | |
| 81 | ||
| 82 | @Override | |
| 83 | public List<Object> getSettingList(final String property, List<String> defaults) { | |
| 84 | if (defaults == null) { | |
| 85 | defaults = new ArrayList<>(); | |
| 86 | } | |
| 87 | ||
| 88 | return getSettings().getList(property, defaults); | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Convert the generic list of property objects into strings. | |
| 93 | * | |
| 94 | * @param property The property value to coerce. | |
| 95 | * @param defaults The defaults values to use should the property be unset. | |
| 96 | * | |
| 97 | * @return The list of properties coerced from objects to strings. | |
| 98 | */ | |
| 99 | @Override | |
| 100 | public List<String> getStringSettingList( | |
| 101 | final String property, final List<String> defaults) { | |
| 102 | final List<Object> settings = getSettingList(property, defaults); | |
| 103 | ||
| 104 | return settings.stream() | |
| 105 | .map(object -> Objects.toString(object, null)) | |
| 106 | .collect(Collectors.toList()); | |
| 107 | } | |
| 108 | ||
| 109 | private PropertiesConfiguration createProperties() | |
| 110 | throws ConfigurationException { | |
| 111 | final URL url = getPropertySource(); | |
| 112 | ||
| 113 | return url == null | |
| 114 | ? new PropertiesConfiguration() | |
| 115 | : new PropertiesConfiguration(url); | |
| 116 | } | |
| 117 | ||
| 118 | private URL getPropertySource() { | |
| 119 | return getClass().getResource(getSettingsFilename()); | |
| 120 | } | |
| 121 | ||
| 122 | private String getSettingsFilename() { | |
| 123 | return SETTINGS_NAME; | |
| 124 | } | |
| 125 | ||
| 126 | private void setProperties(final PropertiesConfiguration configuration) { | |
| 127 | this.properties = configuration; | |
| 128 | } | |
| 129 | ||
| 130 | private PropertiesConfiguration getSettings() { | |
| 131 | return this.properties; | |
| 132 | } | |
| 133 | } | |
| 1 | 134 |
| 1 | /* | |
| 2 | * Copyright 2016 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.ui; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.service.Options; | |
| 32 | import java.util.prefs.Preferences; | |
| 33 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 34 | ||
| 35 | /** | |
| 36 | * Provides options to all subclasses. | |
| 37 | * | |
| 38 | * @author White Magic Software, Ltd. | |
| 39 | */ | |
| 40 | public abstract class AbstractPane extends MigPane { | |
| 41 | ||
| 42 | private final Options options = Services.load( Options.class ); | |
| 43 | ||
| 44 | protected Options getOptions() { | |
| 45 | return this.options; | |
| 46 | } | |
| 47 | ||
| 48 | protected Preferences getState() { | |
| 49 | return getOptions().getState(); | |
| 50 | } | |
| 51 | } | |
| 1 | 52 |
| 1 | /* | |
| 2 | * Copyright 2016 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.ui; | |
| 29 | ||
| 30 | import static com.scrivenvar.Constants.SEPARATOR; | |
| 31 | import com.scrivenvar.decorators.YamlVariableDecorator; | |
| 32 | import com.scrivenvar.decorators.VariableDecorator; | |
| 33 | import static com.scrivenvar.editor.VariableNameInjector.DEFAULT_MAX_VAR_LENGTH; | |
| 34 | import java.util.HashMap; | |
| 35 | import java.util.Map; | |
| 36 | import java.util.Stack; | |
| 37 | import javafx.scene.control.TreeItem; | |
| 38 | ||
| 39 | /** | |
| 40 | * Provides behaviour afforded to variable names and their corresponding value. | |
| 41 | * | |
| 42 | * @author White Magic Software, Ltd. | |
| 43 | * @param <T> The type of TreeItem (usually String). | |
| 44 | */ | |
| 45 | public class VariableTreeItem<T> extends TreeItem<T> { | |
| 46 | ||
| 47 | private final static int DEFAULT_MAP_SIZE = 1000; | |
| 48 | ||
| 49 | private final static VariableDecorator VARIABLE_DECORATOR = | |
| 50 | new YamlVariableDecorator(); | |
| 51 | ||
| 52 | /** | |
| 53 | * Flattened tree. | |
| 54 | */ | |
| 55 | private Map<String, String> map; | |
| 56 | ||
| 57 | /** | |
| 58 | * Constructs a new item with a default value. | |
| 59 | * | |
| 60 | * @param value Passed up to superclass. | |
| 61 | */ | |
| 62 | public VariableTreeItem( final T value ) { | |
| 63 | super( value ); | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Finds a leaf starting at the current node with text that matches the given | |
| 68 | * value. | |
| 69 | * | |
| 70 | * @param text The text to match against each leaf in the tree. | |
| 71 | * | |
| 72 | * @return The leaf that has a value starting with the given text. | |
| 73 | */ | |
| 74 | public VariableTreeItem<T> findLeaf( final String text ) { | |
| 75 | final Stack<VariableTreeItem<T>> stack = new Stack<>(); | |
| 76 | final VariableTreeItem<T> root = this; | |
| 77 | ||
| 78 | stack.push( root ); | |
| 79 | ||
| 80 | boolean found = false; | |
| 81 | VariableTreeItem<T> node = null; | |
| 82 | ||
| 83 | while( !found && !stack.isEmpty() ) { | |
| 84 | node = stack.pop(); | |
| 85 | ||
| 86 | if( node.valueStartsWith( text ) ) { | |
| 87 | found = true; | |
| 88 | } else { | |
| 89 | for( final TreeItem<T> child : node.getChildren() ) { | |
| 90 | stack.push( (VariableTreeItem<T>)child ); | |
| 91 | } | |
| 92 | ||
| 93 | // No match found, yet. | |
| 94 | node = null; | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | return (VariableTreeItem<T>)node; | |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Returns true if this node is a leaf and its value starts with the given | |
| 103 | * text. | |
| 104 | * | |
| 105 | * @param s The text to compare against the node value. | |
| 106 | * | |
| 107 | * @return true Node is a leaf and its value starts with the given value. | |
| 108 | */ | |
| 109 | private boolean valueStartsWith( final String s ) { | |
| 110 | return isLeaf() && getValue().toString().startsWith( s ); | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Returns the path for this node, with nodes made distinct using the | |
| 115 | * separator character. This uses two loops: one for pushing nodes onto a | |
| 116 | * stack and one for popping them off to create the path in desired order. | |
| 117 | * | |
| 118 | * @return A non-null string, possibly empty. | |
| 119 | */ | |
| 120 | public String toPath() { | |
| 121 | final Stack<TreeItem<T>> stack = new Stack<>(); | |
| 122 | TreeItem<T> node = this; | |
| 123 | ||
| 124 | while( node.getParent() != null ) { | |
| 125 | stack.push( node ); | |
| 126 | node = node.getParent(); | |
| 127 | } | |
| 128 | ||
| 129 | final StringBuilder sb = new StringBuilder( DEFAULT_MAX_VAR_LENGTH ); | |
| 130 | ||
| 131 | while( !stack.isEmpty() ) { | |
| 132 | node = stack.pop(); | |
| 133 | ||
| 134 | if( !node.isLeaf() ) { | |
| 135 | sb.append( node.getValue() ); | |
| 136 | ||
| 137 | // This will add a superfluous separator, but instead of peeking at | |
| 138 | // the stack all the time, the last separator will be removed outside | |
| 139 | // the loop (one operation executed once). | |
| 140 | sb.append( SEPARATOR ); | |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | // Remove the trailing SEPARATOR. | |
| 145 | if( sb.length() > 0 ) { | |
| 146 | sb.setLength( sb.length() - 1 ); | |
| 147 | } | |
| 148 | ||
| 149 | return sb.toString(); | |
| 150 | } | |
| 151 | ||
| 152 | /** | |
| 153 | * Returns the hierarchy, flattened to key-value pairs. | |
| 154 | * | |
| 155 | * @return A map of this tree's key-value pairs. | |
| 156 | */ | |
| 157 | public Map<String, String> getMap() { | |
| 158 | if( this.map == null ) { | |
| 159 | this.map = new HashMap<>( DEFAULT_MAP_SIZE ); | |
| 160 | populate( this, this.map ); | |
| 161 | } | |
| 162 | ||
| 163 | return this.map; | |
| 164 | } | |
| 165 | ||
| 166 | private void populate( final TreeItem<T> parent, final Map<String, String> map ) { | |
| 167 | for( final TreeItem<T> child : parent.getChildren() ) { | |
| 168 | if( child.isLeaf() ) { | |
| 169 | @SuppressWarnings( "unchecked" ) | |
| 170 | final String key = toVariable( ((VariableTreeItem<String>)child).toPath() ); | |
| 171 | final String value = child.getValue().toString(); | |
| 172 | ||
| 173 | map.put( key, value ); | |
| 174 | } else { | |
| 175 | populate( child, map ); | |
| 176 | } | |
| 177 | } | |
| 178 | } | |
| 179 | ||
| 180 | /** | |
| 181 | * Converts the name of the key to a simple variable by enclosing it with | |
| 182 | * dollar symbols. | |
| 183 | * | |
| 184 | * @param key The key name to change to a variable. | |
| 185 | * | |
| 186 | * @return $key$ | |
| 187 | */ | |
| 188 | public String toVariable( final String key ) { | |
| 189 | return VARIABLE_DECORATOR.decorate( key ); | |
| 190 | } | |
| 191 | } | |
| 1 | 192 |
| 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 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | import javafx.beans.value.ObservableBooleanValue; | |
| 31 | import javafx.event.ActionEvent; | |
| 32 | import javafx.event.EventHandler; | |
| 33 | import javafx.scene.input.KeyCombination; | |
| 34 | import de.jensd.fx.glyphs.GlyphIcons; | |
| 35 | ||
| 36 | /** | |
| 37 | * Simple action class | |
| 38 | * | |
| 39 | * @author Karl Tauber | |
| 40 | */ | |
| 41 | public class Action | |
| 42 | { | |
| 43 | public final String text; | |
| 44 | public final KeyCombination accelerator; | |
| 45 | public final GlyphIcons icon; | |
| 46 | public final EventHandler<ActionEvent> action; | |
| 47 | public final ObservableBooleanValue disable; | |
| 48 | ||
| 49 | public Action(String text, String accelerator, GlyphIcons icon, | |
| 50 | EventHandler<ActionEvent> action) | |
| 51 | { | |
| 52 | this(text, accelerator, icon, action, null); | |
| 53 | } | |
| 54 | ||
| 55 | public Action(String text, String accelerator, GlyphIcons icon, | |
| 56 | EventHandler<ActionEvent> action, ObservableBooleanValue disable) | |
| 57 | { | |
| 58 | this.text = text; | |
| 59 | this.accelerator = (accelerator != null) ? KeyCombination.valueOf(accelerator) : null; | |
| 60 | this.icon = icon; | |
| 61 | this.action = action; | |
| 62 | this.disable = disable; | |
| 63 | } | |
| 64 | } | |
| 1 | 65 |
| 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 | 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 | * Action utilities | |
| 41 | * | |
| 42 | * @author Karl Tauber | |
| 43 | */ | |
| 44 | public class ActionUtils { | |
| 45 | ||
| 46 | public static Menu createMenu( final String text, final Action... actions ) { | |
| 47 | return new Menu( text, null, createMenuItems( actions ) ); | |
| 48 | } | |
| 49 | ||
| 50 | public static MenuItem[] createMenuItems( Action... actions ) { | |
| 51 | MenuItem[] menuItems = new MenuItem[ actions.length ]; | |
| 52 | for( int i = 0; i < actions.length; i++ ) { | |
| 53 | menuItems[ i ] = (actions[ i ] != null) | |
| 54 | ? createMenuItem( actions[ i ] ) | |
| 55 | : new SeparatorMenuItem(); | |
| 56 | } | |
| 57 | return menuItems; | |
| 58 | } | |
| 59 | ||
| 60 | public static MenuItem createMenuItem( Action action ) { | |
| 61 | MenuItem menuItem = new MenuItem( action.text ); | |
| 62 | if( action.accelerator != null ) { | |
| 63 | menuItem.setAccelerator( action.accelerator ); | |
| 64 | } | |
| 65 | ||
| 66 | if( action.icon != null ) { | |
| 67 | menuItem.setGraphic( FontAwesomeIconFactory.get().createIcon( action.icon ) ); | |
| 68 | } | |
| 69 | ||
| 70 | menuItem.setOnAction( action.action ); | |
| 71 | ||
| 72 | if( action.disable != null ) { | |
| 73 | menuItem.disableProperty().bind( action.disable ); | |
| 74 | } | |
| 75 | ||
| 76 | menuItem.setMnemonicParsing( true ); | |
| 77 | ||
| 78 | return menuItem; | |
| 79 | } | |
| 80 | ||
| 81 | public static ToolBar createToolBar( Action... actions ) { | |
| 82 | return new ToolBar( createToolBarButtons( actions ) ); | |
| 83 | } | |
| 84 | ||
| 85 | public static Node[] createToolBarButtons( Action... actions ) { | |
| 86 | Node[] buttons = new Node[ actions.length ]; | |
| 87 | for( int i = 0; i < actions.length; i++ ) { | |
| 88 | buttons[ i ] = (actions[ i ] != null) | |
| 89 | ? createToolBarButton( actions[ i ] ) | |
| 90 | : new Separator(); | |
| 91 | } | |
| 92 | return buttons; | |
| 93 | } | |
| 94 | ||
| 95 | public static Button createToolBarButton( Action action ) { | |
| 96 | Button button = new Button(); | |
| 97 | button.setGraphic( FontAwesomeIconFactory.get().createIcon( action.icon, "1.2em" ) ); | |
| 98 | String tooltip = action.text; | |
| 99 | if( tooltip.endsWith( "..." ) ) { | |
| 100 | tooltip = tooltip.substring( 0, tooltip.length() - 3 ); | |
| 101 | } | |
| 102 | if( action.accelerator != null ) { | |
| 103 | tooltip += " (" + action.accelerator.getDisplayText() + ')'; | |
| 104 | } | |
| 105 | button.setTooltip( new Tooltip( tooltip ) ); | |
| 106 | button.setFocusTraversable( false ); | |
| 107 | button.setOnAction( action.action ); | |
| 108 | if( action.disable != null ) { | |
| 109 | button.disableProperty().bind( action.disable ); | |
| 110 | } | |
| 111 | return button; | |
| 112 | } | |
| 113 | } | |
| 1 | 114 |
| 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 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | /** | |
| 31 | * Simple item for a ChoiceBox, ComboBox or ListView. | |
| 32 | * Consists of a string name and a value object. | |
| 33 | * toString() returns the name. | |
| 34 | * equals() compares the value and hashCode() returns the hash code of the value. | |
| 35 | * | |
| 36 | * @author Karl Tauber | |
| 37 | */ | |
| 38 | public class Item<V> | |
| 39 | { | |
| 40 | public final String name; | |
| 41 | public final V value; | |
| 42 | ||
| 43 | public Item(String name, V value) { | |
| 44 | this.name = name; | |
| 45 | this.value = value; | |
| 46 | } | |
| 47 | ||
| 48 | @Override | |
| 49 | public boolean equals(Object obj) { | |
| 50 | if (this == obj) | |
| 51 | return true; | |
| 52 | if (!(obj instanceof Item)) | |
| 53 | return false; | |
| 54 | return Utils.safeEquals(value, ((Item<?>)obj).value); | |
| 55 | } | |
| 56 | ||
| 57 | @Override | |
| 58 | public int hashCode() { | |
| 59 | return (value != null) ? value.hashCode() : 0; | |
| 60 | } | |
| 61 | ||
| 62 | @Override | |
| 63 | public String toString() { | |
| 64 | return name; | |
| 65 | } | |
| 66 | } | |
| 1 | 67 |
| 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 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import java.util.prefs.Preferences; | |
| 30 | import javafx.application.Platform; | |
| 31 | import javafx.scene.shape.Rectangle; | |
| 32 | import javafx.stage.Stage; | |
| 33 | import javafx.stage.WindowEvent; | |
| 34 | ||
| 35 | /** | |
| 36 | * Saves and restores Stage state (window bounds, maximized, fullScreen). | |
| 37 | * | |
| 38 | * @author Karl Tauber | |
| 39 | */ | |
| 40 | public class StageState { | |
| 41 | ||
| 42 | public static final String K_PANE_SPLIT_DEFINITION = "pane.split.definition"; | |
| 43 | public static final String K_PANE_SPLIT_EDITOR = "pane.split.editor"; | |
| 44 | ||
| 45 | private final Stage stage; | |
| 46 | private final Preferences state; | |
| 47 | ||
| 48 | private Rectangle normalBounds; | |
| 49 | private boolean runLaterPending; | |
| 50 | ||
| 51 | public StageState( Stage stage, Preferences state ) { | |
| 52 | this.stage = stage; | |
| 53 | this.state = 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 | Rectangle bounds = isNormalState() ? getStageBounds() : normalBounds; | |
| 67 | if( bounds != null ) { | |
| 68 | state.putDouble( "windowX", bounds.getX() ); | |
| 69 | state.putDouble( "windowY", bounds.getY() ); | |
| 70 | state.putDouble( "windowWidth", bounds.getWidth() ); | |
| 71 | state.putDouble( "windowHeight", bounds.getHeight() ); | |
| 72 | } | |
| 73 | state.putBoolean( "windowMaximized", stage.isMaximized() ); | |
| 74 | state.putBoolean( "windowFullScreen", stage.isFullScreen() ); | |
| 75 | } | |
| 76 | ||
| 77 | private void restore() { | |
| 78 | double x = state.getDouble( "windowX", Double.NaN ); | |
| 79 | double y = state.getDouble( "windowY", Double.NaN ); | |
| 80 | double w = state.getDouble( "windowWidth", Double.NaN ); | |
| 81 | double h = state.getDouble( "windowHeight", Double.NaN ); | |
| 82 | boolean maximized = state.getBoolean( "windowMaximized", false ); | |
| 83 | boolean fullScreen = state.getBoolean( "windowFullScreen", false ); | |
| 84 | ||
| 85 | if( !Double.isNaN( x ) && !Double.isNaN( y ) ) { | |
| 86 | stage.setX( x ); | |
| 87 | stage.setY( y ); | |
| 88 | } // else: default behavior is center on screen | |
| 89 | ||
| 90 | if( !Double.isNaN( w ) && !Double.isNaN( h ) ) { | |
| 91 | stage.setWidth( w ); | |
| 92 | stage.setHeight( h ); | |
| 93 | } // else: default behavior is use scene size | |
| 94 | ||
| 95 | if( fullScreen != stage.isFullScreen() ) { | |
| 96 | stage.setFullScreen( fullScreen ); | |
| 97 | } | |
| 98 | if( maximized != stage.isMaximized() ) { | |
| 99 | stage.setMaximized( maximized ); | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Remembers the window bounds when the window is not iconified, maximized or | |
| 105 | * in fullScreen. | |
| 106 | */ | |
| 107 | private void boundsChanged() { | |
| 108 | // avoid too many (and useless) runLater() invocations | |
| 109 | if( runLaterPending ) { | |
| 110 | return; | |
| 111 | } | |
| 112 | runLaterPending = true; | |
| 113 | ||
| 114 | // must use runLater() to ensure that change of all properties | |
| 115 | // (x, y, width, height, iconified, maximized and fullScreen) | |
| 116 | // has finished | |
| 117 | Platform.runLater( () -> { | |
| 118 | runLaterPending = false; | |
| 119 | ||
| 120 | if( isNormalState() ) { | |
| 121 | normalBounds = getStageBounds(); | |
| 122 | } | |
| 123 | } ); | |
| 124 | } | |
| 125 | ||
| 126 | private boolean isNormalState() { | |
| 127 | return !stage.isIconified() && !stage.isMaximized() && !stage.isFullScreen(); | |
| 128 | } | |
| 129 | ||
| 130 | private Rectangle getStageBounds() { | |
| 131 | return new Rectangle( stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight() ); | |
| 132 | } | |
| 133 | } | |
| 1 | 134 |
| 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 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import java.util.ArrayList; | |
| 30 | import java.util.Set; | |
| 31 | import java.util.prefs.Preferences; | |
| 32 | import javafx.geometry.Orientation; | |
| 33 | import javafx.scene.Node; | |
| 34 | import javafx.scene.control.ScrollBar; | |
| 35 | ||
| 36 | /** | |
| 37 | * @author Karl Tauber | |
| 38 | */ | |
| 39 | public class Utils { | |
| 40 | ||
| 41 | public static boolean safeEquals( Object o1, Object o2 ) { | |
| 42 | if( o1 == o2 ) { | |
| 43 | return true; | |
| 44 | } | |
| 45 | if( o1 == null || o2 == null ) { | |
| 46 | return false; | |
| 47 | } | |
| 48 | return o1.equals( o2 ); | |
| 49 | } | |
| 50 | ||
| 51 | public static boolean isNullOrEmpty( String s ) { | |
| 52 | return s == null || s.isEmpty(); | |
| 53 | } | |
| 54 | ||
| 55 | public static String ltrim( final String s ) { | |
| 56 | int i = 0; | |
| 57 | ||
| 58 | while( i < s.length() && Character.isWhitespace( s.charAt( i ) ) ) { | |
| 59 | i++; | |
| 60 | } | |
| 61 | ||
| 62 | return s.substring( i ); | |
| 63 | } | |
| 64 | ||
| 65 | public static String rtrim( final String s ) { | |
| 66 | int i = s.length() - 1; | |
| 67 | ||
| 68 | while( i >= 0 && Character.isWhitespace( s.charAt( i ) ) ) { | |
| 69 | i--; | |
| 70 | } | |
| 71 | ||
| 72 | return s.substring( 0, i + 1 ); | |
| 73 | } | |
| 74 | ||
| 75 | public static void putPrefs( Preferences prefs, String key, String value, String def ) { | |
| 76 | if( value != def && !value.equals( def ) ) { | |
| 77 | prefs.put( key, value ); | |
| 78 | } else { | |
| 79 | prefs.remove( key ); | |
| 80 | } | |
| 81 | } | |
| 82 | ||
| 83 | public static void putPrefsInt( Preferences prefs, String key, int value, int def ) { | |
| 84 | if( value != def ) { | |
| 85 | prefs.putInt( key, value ); | |
| 86 | } else { | |
| 87 | prefs.remove( key ); | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | public static void putPrefsBoolean( Preferences prefs, String key, boolean value, boolean def ) { | |
| 92 | if( value != def ) { | |
| 93 | prefs.putBoolean( key, value ); | |
| 94 | } else { | |
| 95 | prefs.remove( key ); | |
| 96 | } | |
| 97 | } | |
| 98 | ||
| 99 | public static String[] getPrefsStrings( final Preferences prefs, String key ) { | |
| 100 | final ArrayList<String> arr = new ArrayList<>(); | |
| 101 | ||
| 102 | for( int i = 0; i < 10000; i++ ) { | |
| 103 | final String s = prefs.get( key + (i + 1), null ); | |
| 104 | ||
| 105 | if( s == null ) { | |
| 106 | break; | |
| 107 | } | |
| 108 | arr.add( s ); | |
| 109 | } | |
| 110 | ||
| 111 | return arr.toArray( new String[ arr.size() ] ); | |
| 112 | } | |
| 113 | ||
| 114 | public static void putPrefsStrings( Preferences prefs, String key, String[] strings ) { | |
| 115 | for( int i = 0; i < strings.length; i++ ) { | |
| 116 | prefs.put( key + (i + 1), strings[ i ] ); | |
| 117 | } | |
| 118 | ||
| 119 | for( int i = strings.length; prefs.get( key + (i + 1), null ) != null; i++ ) { | |
| 120 | prefs.remove( key + (i + 1) ); | |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 | public static ScrollBar findVScrollBar( Node node ) { | |
| 125 | final Set<Node> scrollBars = node.lookupAll( ".scroll-bar" ); | |
| 126 | ||
| 127 | for( final Node scrollBar : scrollBars ) { | |
| 128 | if( scrollBar instanceof ScrollBar | |
| 129 | && ((ScrollBar)scrollBar).getOrientation() == Orientation.VERTICAL ) { | |
| 130 | return (ScrollBar)scrollBar; | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | return null; | |
| 135 | } | |
| 136 | } | |
| 1 | 137 |
| 1 | /* | |
| 2 | * Copyright 2016 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.yaml; | |
| 29 | ||
| 30 | import com.fasterxml.jackson.core.JsonGenerationException; | |
| 31 | import com.fasterxml.jackson.core.ObjectCodec; | |
| 32 | import com.fasterxml.jackson.core.io.IOContext; | |
| 33 | import com.fasterxml.jackson.databind.JsonNode; | |
| 34 | import com.fasterxml.jackson.databind.ObjectMapper; | |
| 35 | import com.fasterxml.jackson.databind.node.ObjectNode; | |
| 36 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | |
| 37 | import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; | |
| 38 | import static com.scrivenvar.Constants.SEPARATOR; | |
| 39 | import com.scrivenvar.decorators.VariableDecorator; | |
| 40 | import com.scrivenvar.decorators.YamlVariableDecorator; | |
| 41 | import java.io.IOException; | |
| 42 | import java.io.InputStream; | |
| 43 | import java.io.Writer; | |
| 44 | import java.security.InvalidParameterException; | |
| 45 | import java.text.MessageFormat; | |
| 46 | import java.util.HashMap; | |
| 47 | import java.util.Map; | |
| 48 | import java.util.Map.Entry; | |
| 49 | import java.util.regex.Matcher; | |
| 50 | import java.util.regex.Pattern; | |
| 51 | import org.yaml.snakeyaml.DumperOptions; | |
| 52 | ||
| 53 | /** | |
| 54 | * <p> | |
| 55 | * This program loads a YAML document into memory, scans for variable | |
| 56 | * declarations, then substitutes any self-referential values back into the | |
| 57 | * document. Its output is the given YAML document without any variables. | |
| 58 | * Variables in the YAML document are denoted using a bracketed dollar symbol | |
| 59 | * syntax. For example: $field.name$. Some nomenclature to keep from going | |
| 60 | * squirrely, consider: | |
| 61 | * </p> | |
| 62 | * | |
| 63 | * <pre> | |
| 64 | * root: | |
| 65 | * node: | |
| 66 | * name: $field.name$ | |
| 67 | * field: | |
| 68 | * name: Alan Turing | |
| 69 | * </pre> | |
| 70 | * | |
| 71 | * The various components of the given YAML are called: | |
| 72 | * | |
| 73 | * <ul> | |
| 74 | * <li><code>$field.name$</code> - delimited reference</li> | |
| 75 | * <li><code>field.name</code> - reference</li> | |
| 76 | * <li><code>name</code> - YAML field</li> | |
| 77 | * <li><code>Alan Turing</code> - (dereferenced) field value</li> | |
| 78 | * </ul> | |
| 79 | * | |
| 80 | * @author White Magic Software, Ltd. | |
| 81 | */ | |
| 82 | public class YamlParser { | |
| 83 | ||
| 84 | private final static int GROUP_DELIMITED = 1; | |
| 85 | private final static int GROUP_REFERENCE = 2; | |
| 86 | ||
| 87 | private final static VariableDecorator VARIABLE_DECORATOR | |
| 88 | = new YamlVariableDecorator(); | |
| 89 | ||
| 90 | /** | |
| 91 | * Compiled version of DEFAULT_REGEX. | |
| 92 | */ | |
| 93 | private final static Pattern REGEX_PATTERN | |
| 94 | = Pattern.compile( YamlVariableDecorator.REGEX ); | |
| 95 | ||
| 96 | /** | |
| 97 | * Should be JsonPointer.SEPARATOR, but Jackson YAML uses magic values. | |
| 98 | */ | |
| 99 | private final static char SEPARATOR_YAML = '/'; | |
| 100 | ||
| 101 | /** | |
| 102 | * Start of the Universe (the YAML document node that contains all others). | |
| 103 | */ | |
| 104 | private ObjectNode documentRoot; | |
| 105 | ||
| 106 | /** | |
| 107 | * Map of references to dereferenced field values. | |
| 108 | */ | |
| 109 | private Map<String, String> references; | |
| 110 | ||
| 111 | public YamlParser() { | |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * Returns the given string with all the delimited references swapped with | |
| 116 | * their recursively resolved values. | |
| 117 | * | |
| 118 | * @param text The text to parse with zero or more delimited references to | |
| 119 | * replace. | |
| 120 | * | |
| 121 | * @return The substituted value. | |
| 122 | * | |
| 123 | * @throws InvalidParameterException The text has no associated value. | |
| 124 | */ | |
| 125 | public String substitute( String text ) { | |
| 126 | final Matcher matcher = patternMatch( text ); | |
| 127 | final Map<String, String> map = getReferences(); | |
| 128 | ||
| 129 | while( matcher.find() ) { | |
| 130 | final String key = matcher.group( GROUP_DELIMITED ); | |
| 131 | final String value = map.get( key ); | |
| 132 | ||
| 133 | if( value == null ) { | |
| 134 | missing( text ); | |
| 135 | } else { | |
| 136 | text = text.replace( key, value ); | |
| 137 | } | |
| 138 | } | |
| 139 | ||
| 140 | return text; | |
| 141 | } | |
| 142 | ||
| 143 | /** | |
| 144 | * Returns all the strings with their values resolved in a flat hierarchy. | |
| 145 | * This copies all the keys and resolved values into a new map. | |
| 146 | * | |
| 147 | * @return The new map created with all values having been resolved, | |
| 148 | * recursively. | |
| 149 | * | |
| 150 | * @throws InvalidParameterException A key in the map has no associated value. | |
| 151 | */ | |
| 152 | public Map<String, String> createResolvedMap() { | |
| 153 | final Map<String, String> map = new HashMap<>( 1024 ); | |
| 154 | ||
| 155 | resolve( getDocumentRoot(), "", map ); | |
| 156 | ||
| 157 | return map; | |
| 158 | } | |
| 159 | ||
| 160 | /** | |
| 161 | * Iterate over a given root node (at any level of the tree) and adapt each | |
| 162 | * leaf node. | |
| 163 | * | |
| 164 | * @param rootNode A JSON node (YAML node) to adapt. | |
| 165 | */ | |
| 166 | private void resolve( | |
| 167 | final JsonNode rootNode, final String path, final Map<String, String> map ) { | |
| 168 | ||
| 169 | rootNode.fields().forEachRemaining( | |
| 170 | (Entry<String, JsonNode> leaf) -> resolve( leaf, path, map ) | |
| 171 | ); | |
| 172 | } | |
| 173 | ||
| 174 | /** | |
| 175 | * Recursively adapt each rootNode to a corresponding rootItem. | |
| 176 | * | |
| 177 | * @param rootNode The node to adapt. | |
| 178 | */ | |
| 179 | private void resolve( | |
| 180 | final Entry<String, JsonNode> rootNode, final String path, final Map<String, String> map ) { | |
| 181 | final JsonNode leafNode = rootNode.getValue(); | |
| 182 | final String key = rootNode.getKey(); | |
| 183 | ||
| 184 | if( leafNode.isValueNode() ) { | |
| 185 | final String value = rootNode.getValue().asText(); | |
| 186 | ||
| 187 | map.put( VARIABLE_DECORATOR.decorate( path + key ), substitute( value ) ); | |
| 188 | } | |
| 189 | ||
| 190 | if( leafNode.isObject() ) { | |
| 191 | resolve( leafNode, path + key + SEPARATOR, map ); | |
| 192 | } | |
| 193 | } | |
| 194 | ||
| 195 | /** | |
| 196 | * Reads the first document from the given stream of YAML data and returns a | |
| 197 | * corresponding object that represents the YAML hierarchy. The calling class | |
| 198 | * is responsible for closing the stream. Calling classes should use | |
| 199 | * <code>JsonNode.fields()</code> to walk through the YAML tree of fields. | |
| 200 | * | |
| 201 | * @param in The input stream containing YAML content. | |
| 202 | * | |
| 203 | * @return An object hierarchy to represent the content. | |
| 204 | * | |
| 205 | * @throws IOException Could not read the stream. | |
| 206 | */ | |
| 207 | public JsonNode process( final InputStream in ) throws IOException { | |
| 208 | ||
| 209 | final ObjectNode root = (ObjectNode)getObjectMapper().readTree( in ); | |
| 210 | setDocumentRoot( root ); | |
| 211 | process( root ); | |
| 212 | return getDocumentRoot(); | |
| 213 | } | |
| 214 | ||
| 215 | /** | |
| 216 | * Iterate over a given root node (at any level of the tree) and process each | |
| 217 | * leaf node. | |
| 218 | * | |
| 219 | * @param root A node to process. | |
| 220 | */ | |
| 221 | private void process( final JsonNode root ) { | |
| 222 | root.fields().forEachRemaining( this::process ); | |
| 223 | } | |
| 224 | ||
| 225 | /** | |
| 226 | * Process the given field, which is a named node. This is where the | |
| 227 | * application does the up-front work of mapping references to their fully | |
| 228 | * recursively dereferenced values. | |
| 229 | * | |
| 230 | * @param field The named node. | |
| 231 | */ | |
| 232 | private void process( final Entry<String, JsonNode> field ) { | |
| 233 | final JsonNode node = field.getValue(); | |
| 234 | ||
| 235 | if( node.isObject() ) { | |
| 236 | process( node ); | |
| 237 | } else { | |
| 238 | final JsonNode fieldValue = field.getValue(); | |
| 239 | ||
| 240 | // Only basic data types can be parsed into variable values. For | |
| 241 | // node structures, YAML has a built-in mechanism. | |
| 242 | if( fieldValue.isValueNode() ) { | |
| 243 | try { | |
| 244 | resolve( fieldValue.asText() ); | |
| 245 | } catch( StackOverflowError e ) { | |
| 246 | throw new IllegalArgumentException( | |
| 247 | "Unresolvable: " + node.textValue() + " = " + fieldValue ); | |
| 248 | } | |
| 249 | } | |
| 250 | } | |
| 251 | } | |
| 252 | ||
| 253 | /** | |
| 254 | * Inserts the delimited references and field values into the cache. This will | |
| 255 | * overwrite existing references. | |
| 256 | * | |
| 257 | * @param fieldValue YAML field containing zero or more delimited references. | |
| 258 | * If it contains a delimited reference, the parameter is modified with the | |
| 259 | * dereferenced value before it is returned. | |
| 260 | * | |
| 261 | * @return fieldValue without delimited references. | |
| 262 | */ | |
| 263 | private String resolve( String fieldValue ) { | |
| 264 | final Matcher matcher = patternMatch( fieldValue ); | |
| 265 | ||
| 266 | while( matcher.find() ) { | |
| 267 | final String delimited = matcher.group( GROUP_DELIMITED ); | |
| 268 | final String reference = matcher.group( GROUP_REFERENCE ); | |
| 269 | final String dereference = resolve( lookup( reference ) ); | |
| 270 | ||
| 271 | fieldValue = fieldValue.replace( delimited, dereference ); | |
| 272 | ||
| 273 | // This will perform some superfluous calls by overwriting existing | |
| 274 | // items in the delimited reference map. | |
| 275 | put( delimited, dereference ); | |
| 276 | } | |
| 277 | ||
| 278 | return fieldValue; | |
| 279 | } | |
| 280 | ||
| 281 | /** | |
| 282 | * Inserts a key/value pair into the references map. The map retains | |
| 283 | * references and dereferenced values found in the YAML. If the reference | |
| 284 | * already exists, this will overwrite with a new value. | |
| 285 | * | |
| 286 | * @param delimited The variable name. | |
| 287 | * @param dereferenced The resolved value. | |
| 288 | */ | |
| 289 | private void put( String delimited, String dereferenced ) { | |
| 290 | if( dereferenced.isEmpty() ) { | |
| 291 | missing( delimited ); | |
| 292 | } else { | |
| 293 | getReferences().put( delimited, dereferenced ); | |
| 294 | } | |
| 295 | } | |
| 296 | ||
| 297 | /** | |
| 298 | * Writes the modified YAML document to standard output. | |
| 299 | */ | |
| 300 | private void writeDocument() throws IOException { | |
| 301 | getObjectMapper().writeValue( System.out, getDocumentRoot() ); | |
| 302 | } | |
| 303 | ||
| 304 | /** | |
| 305 | * Called when a delimited reference is dereferenced to an empty string. This | |
| 306 | * should produce a warning for the user. | |
| 307 | * | |
| 308 | * @param delimited Delimited reference with no derived value. | |
| 309 | */ | |
| 310 | private void missing( final String delimited ) { | |
| 311 | throw new InvalidParameterException( | |
| 312 | MessageFormat.format( "Missing value for '{0}'.", delimited ) ); | |
| 313 | } | |
| 314 | ||
| 315 | /** | |
| 316 | * Returns a REGEX_PATTERN matcher for the given text. | |
| 317 | * | |
| 318 | * @param text The text that contains zero or more instances of a | |
| 319 | * REGEX_PATTERN that can be found using the regular expression. | |
| 320 | */ | |
| 321 | private Matcher patternMatch( String text ) { | |
| 322 | return getPattern().matcher( text ); | |
| 323 | } | |
| 324 | ||
| 325 | /** | |
| 326 | * Finds the YAML value for a reference. | |
| 327 | * | |
| 328 | * @param reference References a value in the YAML document. | |
| 329 | * | |
| 330 | * @return The dereferenced value. | |
| 331 | */ | |
| 332 | private String lookup( final String reference ) { | |
| 333 | return getDocumentRoot().at( asPath( reference ) ).asText(); | |
| 334 | } | |
| 335 | ||
| 336 | /** | |
| 337 | * Converts a reference (not delimited) to a path that can be used to find a | |
| 338 | * value that should exist inside the YAML document. | |
| 339 | * | |
| 340 | * @param reference The reference to convert to a YAML document path. | |
| 341 | * | |
| 342 | * @return The reference with a leading slash and its separator characters | |
| 343 | * converted to slashes. | |
| 344 | */ | |
| 345 | private String asPath( final String reference ) { | |
| 346 | return SEPARATOR_YAML + reference.replace( getDelimitedSeparator(), SEPARATOR_YAML ); | |
| 347 | } | |
| 348 | ||
| 349 | /** | |
| 350 | * Sets the parent node for the entire YAML document tree. | |
| 351 | * | |
| 352 | * @param documentRoot The parent node. | |
| 353 | */ | |
| 354 | private void setDocumentRoot( ObjectNode documentRoot ) { | |
| 355 | this.documentRoot = documentRoot; | |
| 356 | } | |
| 357 | ||
| 358 | /** | |
| 359 | * Returns the parent node for the entire YAML document tree. | |
| 360 | * | |
| 361 | * @return The parent node. | |
| 362 | */ | |
| 363 | private ObjectNode getDocumentRoot() { | |
| 364 | return this.documentRoot; | |
| 365 | } | |
| 366 | ||
| 367 | /** | |
| 368 | * Returns the compiled regular expression REGEX_PATTERN used to match | |
| 369 | * delimited references. | |
| 370 | * | |
| 371 | * @return A compiled regex for use with the Matcher. | |
| 372 | */ | |
| 373 | private Pattern getPattern() { | |
| 374 | return REGEX_PATTERN; | |
| 375 | } | |
| 376 | ||
| 377 | /** | |
| 378 | * Returns the list of references mapped to dereferenced values. | |
| 379 | * | |
| 380 | * @return | |
| 381 | */ | |
| 382 | private Map<String, String> getReferences() { | |
| 383 | if( this.references == null ) { | |
| 384 | this.references = createReferences(); | |
| 385 | } | |
| 386 | ||
| 387 | return this.references; | |
| 388 | } | |
| 389 | ||
| 390 | /** | |
| 391 | * Subclasses can override this method to insert their own map. | |
| 392 | * | |
| 393 | * @return An empty HashMap, never null. | |
| 394 | */ | |
| 395 | protected Map<String, String> createReferences() { | |
| 396 | return new HashMap<>(); | |
| 397 | } | |
| 398 | ||
| 399 | private class ResolverYAMLFactory extends YAMLFactory { | |
| 400 | ||
| 401 | @Override | |
| 402 | protected YAMLGenerator _createGenerator( | |
| 403 | final Writer out, final IOContext ctxt ) throws IOException { | |
| 404 | ||
| 405 | return new ResolverYAMLGenerator( | |
| 406 | ctxt, _generatorFeatures, _yamlGeneratorFeatures, _objectCodec, | |
| 407 | out, _version ); | |
| 408 | } | |
| 409 | } | |
| 410 | ||
| 411 | private class ResolverYAMLGenerator extends YAMLGenerator { | |
| 412 | ||
| 413 | public ResolverYAMLGenerator( | |
| 414 | final IOContext ctxt, | |
| 415 | final int jsonFeatures, | |
| 416 | final int yamlFeatures, | |
| 417 | final ObjectCodec codec, | |
| 418 | final Writer out, | |
| 419 | final DumperOptions.Version version ) throws IOException { | |
| 420 | ||
| 421 | super( ctxt, jsonFeatures, yamlFeatures, codec, out, version ); | |
| 422 | } | |
| 423 | ||
| 424 | @Override | |
| 425 | public void writeString( final String text ) | |
| 426 | throws IOException, JsonGenerationException { | |
| 427 | super.writeString( substitute( text ) ); | |
| 428 | } | |
| 429 | } | |
| 430 | ||
| 431 | private YAMLFactory getYAMLFactory() { | |
| 432 | return new ResolverYAMLFactory(); | |
| 433 | } | |
| 434 | ||
| 435 | private ObjectMapper getObjectMapper() { | |
| 436 | return new ObjectMapper( getYAMLFactory() ); | |
| 437 | } | |
| 438 | ||
| 439 | /** | |
| 440 | * Returns the character used to separate YAML paths within delimited | |
| 441 | * references. This will return only the first character of the command line | |
| 442 | * parameter, if the default is overridden. | |
| 443 | * | |
| 444 | * @return A period by default. | |
| 445 | */ | |
| 446 | private char getDelimitedSeparator() { | |
| 447 | return SEPARATOR.charAt( 0 ); | |
| 448 | } | |
| 449 | } | |
| 1 | 450 |
| 1 | /* | |
| 2 | * Copyright 2016 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.yaml; | |
| 29 | ||
| 30 | import com.fasterxml.jackson.databind.JsonNode; | |
| 31 | import com.scrivenvar.ui.VariableTreeItem; | |
| 32 | import java.io.IOException; | |
| 33 | import java.io.InputStream; | |
| 34 | import java.util.Map.Entry; | |
| 35 | import javafx.scene.control.TreeItem; | |
| 36 | import javafx.scene.control.TreeView; | |
| 37 | ||
| 38 | /** | |
| 39 | * Transforms a JsonNode hierarchy into a tree that can be displayed in a user | |
| 40 | * interface. | |
| 41 | * | |
| 42 | * @author White Magic Software, Ltd. | |
| 43 | */ | |
| 44 | public class YamlTreeAdapter { | |
| 45 | ||
| 46 | private YamlParser yamlParser; | |
| 47 | ||
| 48 | public YamlTreeAdapter( final YamlParser parser ) { | |
| 49 | setYamlParser( parser ); | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Converts a YAML document to a TreeView based on the document keys. Only the | |
| 54 | * first document in the stream is adapted. This does not close the stream. | |
| 55 | * | |
| 56 | * @param in Contains a YAML document. | |
| 57 | * @param name Name of the root TreeItem. | |
| 58 | * | |
| 59 | * @return A TreeView populated with all the keys in the YAML document. | |
| 60 | * | |
| 61 | * @throws IOException Could not read from the stream. | |
| 62 | */ | |
| 63 | public TreeView<String> adapt( final InputStream in, final String name ) | |
| 64 | throws IOException { | |
| 65 | ||
| 66 | final JsonNode rootNode = getYamlParser().process( in ); | |
| 67 | final TreeItem<String> rootItem = createTreeItem( name ); | |
| 68 | ||
| 69 | rootItem.setExpanded( true ); | |
| 70 | adapt( rootNode, rootItem ); | |
| 71 | return new TreeView<>( rootItem ); | |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Iterate over a given root node (at any level of the tree) and adapt each | |
| 76 | * leaf node. | |
| 77 | * | |
| 78 | * @param rootNode A JSON node (YAML node) to adapt. | |
| 79 | * @param rootItem The tree item to use as the root when processing the node. | |
| 80 | */ | |
| 81 | private void adapt( | |
| 82 | final JsonNode rootNode, final TreeItem<String> rootItem ) { | |
| 83 | ||
| 84 | rootNode.fields().forEachRemaining( | |
| 85 | (Entry<String, JsonNode> leaf) -> adapt( leaf, rootItem ) | |
| 86 | ); | |
| 87 | } | |
| 88 | ||
| 89 | /** | |
| 90 | * Recursively adapt each rootNode to a corresponding rootItem. | |
| 91 | * | |
| 92 | * @param rootNode The node to adapt. | |
| 93 | * @param rootItem The item to adapt using the node's key. | |
| 94 | */ | |
| 95 | private void adapt( | |
| 96 | final Entry<String, JsonNode> rootNode, final TreeItem<String> rootItem ) { | |
| 97 | ||
| 98 | final JsonNode leafNode = rootNode.getValue(); | |
| 99 | final String key = rootNode.getKey(); | |
| 100 | final TreeItem<String> leaf = createTreeItem( key ); | |
| 101 | ||
| 102 | if( leafNode.isValueNode() ) { | |
| 103 | leaf.getChildren().add( createTreeItem( rootNode.getValue().asText() ) ); | |
| 104 | } | |
| 105 | ||
| 106 | rootItem.getChildren().add( leaf ); | |
| 107 | ||
| 108 | if( leafNode.isObject() ) { | |
| 109 | adapt( leafNode, leaf ); | |
| 110 | } | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Creates a new tree item that can be added to the tree view. | |
| 115 | * | |
| 116 | * @param value The node's value. | |
| 117 | * | |
| 118 | * @return A new tree item node, never null. | |
| 119 | */ | |
| 120 | private TreeItem<String> createTreeItem( final String value ) { | |
| 121 | return new VariableTreeItem<>( value ); | |
| 122 | } | |
| 123 | ||
| 124 | private YamlParser getYamlParser() { | |
| 125 | return this.yamlParser; | |
| 126 | } | |
| 127 | ||
| 128 | private void setYamlParser( final YamlParser yamlParser ) { | |
| 129 | this.yamlParser = yamlParser; | |
| 130 | } | |
| 131 | ||
| 132 | } | |
| 1 | 133 |
| 1 | ||
| 1 | com.scrivenvar.service.impl.DefaultOptions |
| 1 | ||
| 1 | com.scrivenvar.service.impl.DefaultSettings |
| 1 | ||
| 1 | com.scrivenvar.service.events.impl.DefaultAlertService |
| 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 | .markdown-editor { | |
| 29 | -fx-font-size: 14px; | |
| 30 | } | |
| 31 | ||
| 32 | /*---- headers ----*/ | |
| 33 | ||
| 34 | .markdown-editor .h1 { -fx-font-size: 2.25em; } | |
| 35 | .markdown-editor .h2 { -fx-font-size: 1.75em; } | |
| 36 | .markdown-editor .h3 { -fx-font-size: 1.5em; } | |
| 37 | .markdown-editor .h4 { -fx-font-size: 1.25em; } | |
| 38 | .markdown-editor .h5 { -fx-font-size: 1.1em; } | |
| 39 | .markdown-editor .h6 { -fx-font-size: 1em; } | |
| 40 | ||
| 41 | .markdown-editor .h1, | |
| 42 | .markdown-editor .h2, | |
| 43 | .markdown-editor .h3, | |
| 44 | .markdown-editor .h4, | |
| 45 | .markdown-editor .h5, | |
| 46 | .markdown-editor .h6 { | |
| 47 | -fx-font-weight: bold; | |
| 48 | -fx-fill: derive(crimson, -20%); | |
| 49 | } | |
| 50 | ||
| 51 | ||
| 52 | /*---- inlines ----*/ | |
| 53 | ||
| 54 | .markdown-editor .strong { | |
| 55 | -fx-font-weight: bold; | |
| 56 | } | |
| 57 | ||
| 58 | .markdown-editor .em { | |
| 59 | -fx-font-style: italic; | |
| 60 | } | |
| 61 | ||
| 62 | .markdown-editor .del { | |
| 63 | -fx-strikethrough: true; | |
| 64 | } | |
| 65 | ||
| 66 | .markdown-editor .a { | |
| 67 | -fx-fill: #4183C4 !important; | |
| 68 | } | |
| 69 | ||
| 70 | .markdown-editor .img { | |
| 71 | -fx-fill: #4183C4 !important; | |
| 72 | } | |
| 73 | ||
| 74 | .markdown-editor .code { | |
| 75 | -fx-font-family: monospace; | |
| 76 | -fx-fill: #090 !important; | |
| 77 | } | |
| 78 | ||
| 79 | ||
| 80 | /*---- blocks ----*/ | |
| 81 | ||
| 82 | .markdown-editor .pre { | |
| 83 | -fx-font-family: monospace; | |
| 84 | -fx-fill: #060 !important; | |
| 85 | } | |
| 86 | ||
| 87 | .markdown-editor .blockquote { | |
| 88 | -fx-fill: #777; | |
| 89 | } | |
| 90 | ||
| 91 | ||
| 92 | /*---- lists ----*/ | |
| 93 | ||
| 94 | .markdown-editor .ul { | |
| 95 | } | |
| 96 | ||
| 97 | .markdown-editor .ol { | |
| 98 | } | |
| 99 | ||
| 100 | .markdown-editor .li { | |
| 101 | -fx-fill: #444; | |
| 102 | } | |
| 103 | ||
| 104 | .markdown-editor .dl { | |
| 105 | } | |
| 106 | ||
| 107 | .markdown-editor .dt { | |
| 108 | -fx-font-weight: bold; | |
| 109 | -fx-font-style: italic; | |
| 110 | } | |
| 111 | ||
| 112 | .markdown-editor .dd { | |
| 113 | -fx-fill: #444; | |
| 114 | } | |
| 115 | ||
| 116 | ||
| 117 | /*---- table ----*/ | |
| 118 | ||
| 119 | .markdown-editor .table { | |
| 120 | -fx-font-family: monospace; | |
| 121 | } | |
| 122 | ||
| 123 | .markdown-editor .thead { | |
| 124 | } | |
| 125 | ||
| 126 | .markdown-editor .tbody { | |
| 127 | } | |
| 128 | ||
| 129 | .markdown-editor .caption { | |
| 130 | } | |
| 131 | ||
| 132 | .markdown-editor .th { | |
| 133 | -fx-font-weight: bold; | |
| 134 | } | |
| 135 | ||
| 136 | .markdown-editor .tr { | |
| 137 | } | |
| 138 | ||
| 139 | .markdown-editor .td { | |
| 140 | } | |
| 141 | ||
| 142 | ||
| 143 | /*---- misc ----*/ | |
| 144 | ||
| 145 | .markdown-editor .html { | |
| 146 | -fx-font-family: monospace; | |
| 147 | -fx-fill: derive(crimson, -50%); | |
| 148 | } | |
| 149 | .markdown-editor .monospace { | |
| 150 | -fx-font-family: monospace; | |
| 151 | } | |
| 1 | 152 |
| 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 | # 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 | # * Redistributions of source code must retain the above copyright | |
| 10 | # notice, this list of conditions and the following disclaimer. | |
| 11 | # | |
| 12 | # * 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 | # ######################################################################## | |
| 30 | # | |
| 31 | # Main Application Window | |
| 32 | # | |
| 33 | # ######################################################################## | |
| 34 | ||
| 35 | # The application title should exist only once in the entire code base. | |
| 36 | # All other references should either refer to this value via the Messages | |
| 37 | # class, or indirectly using ${Main.title}. | |
| 38 | Main.title=Scrivenvar | |
| 39 | ||
| 40 | Main.menu.file=_File | |
| 41 | Main.menu.file.new=New | |
| 42 | Main.menu.file.open=Open... | |
| 43 | Main.menu.file.close=Close | |
| 44 | Main.menu.file.close_all=Close All | |
| 45 | Main.menu.file.save=Save | |
| 46 | Main.menu.file.save_all=Save All | |
| 47 | Main.menu.file.exit=Exit | |
| 48 | ||
| 49 | Main.menu.edit=_Edit | |
| 50 | Main.menu.edit.undo=Undo | |
| 51 | Main.menu.edit.redo=Redo | |
| 52 | ||
| 53 | Main.menu.insert=_Insert | |
| 54 | Main.menu.insert.bold=Bold | |
| 55 | Main.menu.insert.italic=Italic | |
| 56 | Main.menu.insert.strikethrough=Strikethrough | |
| 57 | Main.menu.insert.blockquote=Blockquote | |
| 58 | Main.menu.insert.code=Inline Code | |
| 59 | Main.menu.insert.fenced_code_block=Fenced Code Block | |
| 60 | Main.menu.insert.fenced_code_block.prompt=Enter code here | |
| 61 | Main.menu.insert.link=Link... | |
| 62 | Main.menu.insert.image=Image... | |
| 63 | Main.menu.insert.header_1=Header 1 | |
| 64 | Main.menu.insert.header_1.prompt=header 1 | |
| 65 | Main.menu.insert.header_2=Header 2 | |
| 66 | Main.menu.insert.header_2.prompt=header 2 | |
| 67 | Main.menu.insert.header_3=Header 3 | |
| 68 | Main.menu.insert.header_3.prompt=header 3 | |
| 69 | Main.menu.insert.header_4=Header 4 | |
| 70 | Main.menu.insert.header_4.prompt=header 4 | |
| 71 | Main.menu.insert.header_5=Header 5 | |
| 72 | Main.menu.insert.header_5.prompt=header 5 | |
| 73 | Main.menu.insert.header_6=Header 6 | |
| 74 | Main.menu.insert.header_6.prompt=header 6 | |
| 75 | Main.menu.insert.unordered_list=Unordered List | |
| 76 | Main.menu.insert.ordered_list=Ordered List | |
| 77 | Main.menu.insert.horizontal_rule=Horizontal Rule | |
| 78 | ||
| 79 | Main.menu.tools=_Tools | |
| 80 | Main.menu.tools.options=Options | |
| 81 | ||
| 82 | Main.menu.help=_Help | |
| 83 | Main.menu.help.about=About ${Main.title} | |
| 84 | ||
| 85 | # ######################################################################## | |
| 86 | # | |
| 87 | # About Dialog | |
| 88 | # | |
| 89 | # ######################################################################## | |
| 90 | ||
| 91 | Dialog.about.title=About | |
| 92 | Dialog.about.header=${Main.title} | |
| 93 | Dialog.about.content=Copyright 2016 White Magic Software, Ltd.\n\nBased on Markdown Writer FX by Karl Tauber | |
| 94 | ||
| 95 | # ######################################################################## | |
| 96 | # | |
| 97 | # File Editor | |
| 98 | # | |
| 99 | # ######################################################################## | |
| 100 | ||
| 101 | FileEditor.untitled=Untitled | |
| 102 | FileEditor.loadFailed.message=Failed to load ''{0}''.\n\nReason: {1} | |
| 103 | FileEditor.loadFailed.title=Load | |
| 104 | FileEditor.saveFailed.message=Failed to save ''{0}''.\n\nReason: {1} | |
| 105 | FileEditor.saveFailed.title=Save | |
| 106 | ||
| 107 | # ######################################################################## | |
| 108 | # | |
| 109 | # File Open | |
| 110 | # | |
| 111 | # ######################################################################## | |
| 112 | ||
| 113 | Dialog.file.choose.open.title=Open File | |
| 114 | Dialog.file.choose.save.title=Save File | |
| 115 | ||
| 116 | Dialog.file.choose.filter.title.markdown=Markdown Files | |
| 117 | Dialog.file.choose.filter.title.definition=Definition Files | |
| 118 | Dialog.file.choose.filter.title.xml=XML Files | |
| 119 | Dialog.file.choose.filter.title.all=All Files | |
| 120 | ||
| 121 | # ######################################################################## | |
| 122 | # | |
| 123 | # Alert Dialog | |
| 124 | # | |
| 125 | # ######################################################################## | |
| 126 | ||
| 127 | Alert.file.close.title=Close | |
| 128 | Alert.file.close.text=Save changes to {0}? | |
| 129 | ||
| 130 | # ######################################################################## | |
| 131 | # | |
| 132 | # Definition Pane | |
| 133 | # | |
| 134 | # ######################################################################## | |
| 135 | ||
| 136 | Pane.defintion.node.root.title=Definitions | |
| 137 | ||
| 138 | # Controls ############################################################### | |
| 139 | ||
| 140 | # ######################################################################## | |
| 141 | # | |
| 142 | # Browse Directory | |
| 143 | # | |
| 144 | # ######################################################################## | |
| 145 | ||
| 146 | BrowseDirectoryButton.chooser.title=Browse for local folder | |
| 147 | BrowseDirectoryButton.tooltip=${BrowseDirectoryButton.chooser.title} | |
| 148 | ||
| 149 | # ######################################################################## | |
| 150 | # | |
| 151 | # Browse File | |
| 152 | # | |
| 153 | # ######################################################################## | |
| 154 | ||
| 155 | BrowseFileButton.chooser.title=Browse for local file | |
| 156 | BrowseFileButton.chooser.allFilesFilter=All Files | |
| 157 | BrowseFileButton.tooltip=${BrowseFileButton.chooser.title} | |
| 158 | ||
| 159 | # Dialogs ################################################################ | |
| 160 | ||
| 161 | # ######################################################################## | |
| 162 | # | |
| 163 | # Image | |
| 164 | # | |
| 165 | # ######################################################################## | |
| 166 | ||
| 167 | ImageDialog.title=Image | |
| 168 | ImageDialog.chooser.imagesFilter=Images | |
| 169 | ImageDialog.previewLabel.text=Markdown Preview\: | |
| 170 | ImageDialog.textLabel.text=Alternate Text\: | |
| 171 | ImageDialog.titleLabel.text=Title (tooltip)\: | |
| 172 | ImageDialog.urlLabel.text=Image URL\: | |
| 173 | ||
| 174 | # ######################################################################## | |
| 175 | # | |
| 176 | # Hyperlink | |
| 177 | # | |
| 178 | # ######################################################################## | |
| 179 | ||
| 180 | LinkDialog.title=Link | |
| 181 | LinkDialog.previewLabel.text=Markdown Preview\: | |
| 182 | LinkDialog.textLabel.text=Link Text\: | |
| 183 | LinkDialog.titleLabel.text=Title (tooltip)\: | |
| 184 | LinkDialog.urlLabel.text=Link URL\: | |
| 185 | ||
| 186 | # Options ################################################################ | |
| 187 | ||
| 188 | # ######################################################################## | |
| 189 | # | |
| 190 | # Options Dialog | |
| 191 | # | |
| 192 | # ######################################################################## | |
| 193 | ||
| 194 | OptionsDialog.title=Options | |
| 195 | OptionsDialog.generalTab.text=General | |
| 196 | OptionsDialog.markdownTab.text=Markdown | |
| 197 | ||
| 198 | # ######################################################################## | |
| 199 | # | |
| 200 | # General Options Pane | |
| 201 | # | |
| 202 | # ######################################################################## | |
| 203 | ||
| 204 | GeneralOptionsPane.encodingLabel.text=En_coding\: | |
| 205 | GeneralOptionsPane.lineSeparatorLabel.text=_Line separator\: | |
| 206 | GeneralOptionsPane.lineSeparatorLabel2.text=(applies to new files only) | |
| 207 | ||
| 208 | GeneralOptionsPane.platformDefault=Platform Default ({0}) | |
| 209 | GeneralOptionsPane.sepWindows=Windows (CRLF) | |
| 210 | GeneralOptionsPane.sepUnix=Unix (LF) | |
| 211 | ||
| 212 | # ######################################################################## | |
| 213 | # | |
| 214 | # Markdown Options Pane | |
| 215 | # | |
| 216 | # ######################################################################## | |
| 217 | ||
| 218 | MarkdownOptionsPane.abbreviationsExtCheckBox.text=A_bbreviations in the way of | |
| 219 | MarkdownOptionsPane.abbreviationsExtLink.text=Markdown Extra | |
| 220 | MarkdownOptionsPane.anchorlinksExtCheckBox.text=_Anchor links in headers | |
| 221 | MarkdownOptionsPane.atxHeaderSpaceExtCheckBox.text=Requires a space char after Atx \# header prefixes, so that \#dasdsdaf is not a header | |
| 222 | MarkdownOptionsPane.autolinksExtCheckBox.text=_Plain (undelimited) autolinks in the way of | |
| 223 | MarkdownOptionsPane.autolinksExtLink.text=Github-flavoured-Markdown | |
| 224 | MarkdownOptionsPane.definitionListsExtCheckBox.text=_Definition lists in the way of | |
| 225 | MarkdownOptionsPane.definitionListsExtLink.text=Markdown Extra | |
| 226 | MarkdownOptionsPane.extAnchorLinksExtCheckBox.text=Generate anchor links for headers using complete contents of the header | |
| 227 | MarkdownOptionsPane.fencedCodeBlocksExtCheckBox.text=_Fenced Code Blocks in the way of | |
| 228 | MarkdownOptionsPane.fencedCodeBlocksExtLabel.text=or | |
| 229 | MarkdownOptionsPane.fencedCodeBlocksExtLink.text=Markdown Extra | |
| 230 | MarkdownOptionsPane.fencedCodeBlocksExtLink2.text=Github-flavoured-Markdown | |
| 231 | MarkdownOptionsPane.forceListItemParaExtCheckBox.text=Force List and Definition Paragraph wrapping if it includes more than just a single paragraph | |
| 232 | MarkdownOptionsPane.hardwrapsExtCheckBox.text=_Newlines in paragraph-like content as real line breaks, see | |
| 233 | MarkdownOptionsPane.hardwrapsExtLink.text=Github-flavoured-Markdown | |
| 234 | MarkdownOptionsPane.quotesExtCheckBox.text=Beautify single _quotes, double quotes and double angle quotes (\u00ab and \u00bb) | |
| 235 | MarkdownOptionsPane.relaxedHrRulesExtCheckBox.text=Allow horizontal rules without a blank line following them | |
| 236 | MarkdownOptionsPane.smartsExtCheckBox.text=Beautify apostrophes, _ellipses ("..." and ". . .") and dashes ("--" and "---") | |
| 237 | MarkdownOptionsPane.strikethroughExtCheckBox.text=_Strikethrough | |
| 238 | MarkdownOptionsPane.suppressHtmlBlocksExtCheckBox.text=Suppress the _output of HTML blocks | |
| 239 | MarkdownOptionsPane.suppressInlineHtmlExtCheckBox.text=Suppress the o_utput of inline HTML elements | |
| 240 | MarkdownOptionsPane.tablesExtCheckBox.text=_Tables similar to | |
| 241 | MarkdownOptionsPane.tablesExtLabel.text=(like | |
| 242 | MarkdownOptionsPane.tablesExtLabel2.text=tables, but with colspan support) | |
| 243 | MarkdownOptionsPane.tablesExtLink.text=MultiMarkdown | |
| 244 | MarkdownOptionsPane.tablesExtLink2.text=Markdown Extra | |
| 245 | MarkdownOptionsPane.taskListItemsExtCheckBox.text=GitHub style task list items | |
| 246 | MarkdownOptionsPane.wikilinksExtCheckBox.text=_Wiki-style links ("[[wiki link]]") | |
| 1 | 247 |
| 1 | /* | |
| 2 | This software is released under the MIT license: | |
| 3 | ||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | |
| 5 | this software and associated documentation files (the "Software"), to deal in | |
| 6 | the Software without restriction, including without limitation the rights to | |
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |
| 8 | the Software, and to permit persons to whom the Software is furnished to do so, | |
| 9 | subject to the following conditions: | |
| 10 | ||
| 11 | The above copyright notice and this permission notice shall be included in all | |
| 12 | copies or substantial portions of the Software. | |
| 13 | ||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |
| 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |
| 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 20 | */ | |
| 21 | ||
| 22 | /* Source: https://github.com/nicolashery/markdownpad-github */ | |
| 23 | ||
| 24 | /* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */ | |
| 25 | ||
| 26 | /* RESET | |
| 27 | =============================================================================*/ | |
| 28 | ||
| 29 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { | |
| 30 | margin: 0; | |
| 31 | padding: 0; | |
| 32 | border: 0; | |
| 33 | } | |
| 34 | ||
| 35 | /* BODY | |
| 36 | =============================================================================*/ | |
| 37 | ||
| 38 | body { | |
| 39 | font-family: Helvetica, arial, freesans, clean, sans-serif; | |
| 40 | font-size: 14px; | |
| 41 | line-height: 1.6; | |
| 42 | color: #333; | |
| 43 | background-color: #fff; | |
| 44 | padding: 20px; | |
| 45 | max-width: 960px; | |
| 46 | margin: 0 auto; | |
| 47 | } | |
| 48 | ||
| 49 | body>*:first-child { | |
| 50 | margin-top: 0 !important; | |
| 51 | } | |
| 52 | ||
| 53 | body>*:last-child { | |
| 54 | margin-bottom: 0 !important; | |
| 55 | } | |
| 56 | ||
| 57 | /* BLOCKS | |
| 58 | =============================================================================*/ | |
| 59 | ||
| 60 | p, blockquote, ul, ol, dl, table, pre { | |
| 61 | margin: 15px 0; | |
| 62 | } | |
| 63 | ||
| 64 | /* HEADERS | |
| 65 | =============================================================================*/ | |
| 66 | ||
| 67 | h1, h2, h3, h4, h5, h6 { | |
| 68 | margin: 20px 0 10px; | |
| 69 | padding: 0; | |
| 70 | font-weight: bold; | |
| 71 | -webkit-font-smoothing: antialiased; | |
| 72 | } | |
| 73 | ||
| 74 | h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code { | |
| 75 | font-size: inherit; | |
| 76 | } | |
| 77 | ||
| 78 | h1 { | |
| 79 | font-size: 28px; | |
| 80 | color: #000; | |
| 81 | } | |
| 82 | ||
| 83 | h2 { | |
| 84 | font-size: 24px; | |
| 85 | border-bottom: 1px solid #ccc; | |
| 86 | color: #000; | |
| 87 | } | |
| 88 | ||
| 89 | h3 { | |
| 90 | font-size: 18px; | |
| 91 | } | |
| 92 | ||
| 93 | h4 { | |
| 94 | font-size: 16px; | |
| 95 | } | |
| 96 | ||
| 97 | h5 { | |
| 98 | font-size: 14px; | |
| 99 | } | |
| 100 | ||
| 101 | h6 { | |
| 102 | color: #777; | |
| 103 | font-size: 14px; | |
| 104 | } | |
| 105 | ||
| 106 | body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child { | |
| 107 | margin-top: 0; | |
| 108 | padding-top: 0; | |
| 109 | } | |
| 110 | ||
| 111 | a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 { | |
| 112 | margin-top: 0; | |
| 113 | padding-top: 0; | |
| 114 | } | |
| 115 | ||
| 116 | h1+p, h2+p, h3+p, h4+p, h5+p, h6+p { | |
| 117 | margin-top: 10px; | |
| 118 | } | |
| 119 | ||
| 120 | /* LINKS | |
| 121 | =============================================================================*/ | |
| 122 | ||
| 123 | a { | |
| 124 | color: #4183C4; | |
| 125 | text-decoration: none; | |
| 126 | } | |
| 127 | ||
| 128 | a:hover { | |
| 129 | text-decoration: underline; | |
| 130 | } | |
| 131 | ||
| 132 | /* LISTS | |
| 133 | =============================================================================*/ | |
| 134 | ||
| 135 | ul, ol { | |
| 136 | padding-left: 30px; | |
| 137 | } | |
| 138 | ||
| 139 | ul li > :first-child, | |
| 140 | ol li > :first-child, | |
| 141 | ul li ul:first-of-type, | |
| 142 | ol li ol:first-of-type, | |
| 143 | ul li ol:first-of-type, | |
| 144 | ol li ul:first-of-type { | |
| 145 | margin-top: 0px; | |
| 146 | } | |
| 147 | ||
| 148 | ul ul, ul ol, ol ol, ol ul { | |
| 149 | margin-bottom: 0; | |
| 150 | } | |
| 151 | ||
| 152 | dl { | |
| 153 | padding: 0; | |
| 154 | } | |
| 155 | ||
| 156 | dl dt { | |
| 157 | font-size: 14px; | |
| 158 | font-weight: bold; | |
| 159 | font-style: italic; | |
| 160 | padding: 0; | |
| 161 | margin: 15px 0 5px; | |
| 162 | } | |
| 163 | ||
| 164 | dl dt:first-child { | |
| 165 | padding: 0; | |
| 166 | } | |
| 167 | ||
| 168 | dl dt>:first-child { | |
| 169 | margin-top: 0px; | |
| 170 | } | |
| 171 | ||
| 172 | dl dt>:last-child { | |
| 173 | margin-bottom: 0px; | |
| 174 | } | |
| 175 | ||
| 176 | dl dd { | |
| 177 | margin: 0 0 15px; | |
| 178 | padding: 0 15px; | |
| 179 | } | |
| 180 | ||
| 181 | dl dd>:first-child { | |
| 182 | margin-top: 0px; | |
| 183 | } | |
| 184 | ||
| 185 | dl dd>:last-child { | |
| 186 | margin-bottom: 0px; | |
| 187 | } | |
| 188 | ||
| 189 | /* CODE | |
| 190 | =============================================================================*/ | |
| 191 | ||
| 192 | pre, code, tt { | |
| 193 | font-size: 12px; | |
| 194 | font-family: Consolas, "Liberation Mono", Courier, monospace; | |
| 195 | } | |
| 196 | ||
| 197 | code, tt { | |
| 198 | margin: 0 0px; | |
| 199 | padding: 0px 0px; | |
| 200 | white-space: nowrap; | |
| 201 | border: 1px solid #eaeaea; | |
| 202 | background-color: #f8f8f8; | |
| 203 | border-radius: 3px; | |
| 204 | } | |
| 205 | ||
| 206 | pre>code { | |
| 207 | margin: 0; | |
| 208 | padding: 0; | |
| 209 | white-space: pre; | |
| 210 | border: none; | |
| 211 | background: transparent; | |
| 212 | } | |
| 213 | ||
| 214 | pre { | |
| 215 | background-color: #f8f8f8; | |
| 216 | border: 1px solid #ccc; | |
| 217 | font-size: 13px; | |
| 218 | line-height: 19px; | |
| 219 | overflow: auto; | |
| 220 | padding: 6px 10px; | |
| 221 | border-radius: 3px; | |
| 222 | } | |
| 223 | ||
| 224 | pre code, pre tt { | |
| 225 | background-color: transparent; | |
| 226 | border: none; | |
| 227 | } | |
| 228 | ||
| 229 | kbd { | |
| 230 | -moz-border-bottom-colors: none; | |
| 231 | -moz-border-left-colors: none; | |
| 232 | -moz-border-right-colors: none; | |
| 233 | -moz-border-top-colors: none; | |
| 234 | background-color: #DDDDDD; | |
| 235 | background-image: linear-gradient(#F1F1F1, #DDDDDD); | |
| 236 | background-repeat: repeat-x; | |
| 237 | border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD; | |
| 238 | border-image: none; | |
| 239 | border-radius: 2px 2px 2px 2px; | |
| 240 | border-style: solid; | |
| 241 | border-width: 1px; | |
| 242 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; | |
| 243 | line-height: 10px; | |
| 244 | padding: 1px 4px; | |
| 245 | } | |
| 246 | ||
| 247 | /* QUOTES | |
| 248 | =============================================================================*/ | |
| 249 | ||
| 250 | blockquote { | |
| 251 | border-left: 4px solid #DDD; | |
| 252 | padding: 0 15px; | |
| 253 | color: #777; | |
| 254 | } | |
| 255 | ||
| 256 | blockquote>:first-child { | |
| 257 | margin-top: 0px; | |
| 258 | } | |
| 259 | ||
| 260 | blockquote>:last-child { | |
| 261 | margin-bottom: 0px; | |
| 262 | } | |
| 263 | ||
| 264 | /* HORIZONTAL RULES | |
| 265 | =============================================================================*/ | |
| 266 | ||
| 267 | hr { | |
| 268 | clear: both; | |
| 269 | margin: 15px 0; | |
| 270 | height: 0px; | |
| 271 | overflow: hidden; | |
| 272 | border: none; | |
| 273 | background: transparent; | |
| 274 | border-bottom: 4px solid #ddd; | |
| 275 | padding: 0; | |
| 276 | } | |
| 277 | ||
| 278 | /* TABLES | |
| 279 | =============================================================================*/ | |
| 280 | ||
| 281 | table th { | |
| 282 | font-weight: bold; | |
| 283 | } | |
| 284 | ||
| 285 | table th, table td { | |
| 286 | border: 1px solid #ccc; | |
| 287 | padding: 6px 13px; | |
| 288 | } | |
| 289 | ||
| 290 | table tr { | |
| 291 | border-top: 1px solid #ccc; | |
| 292 | background-color: #fff; | |
| 293 | } | |
| 294 | ||
| 295 | table tr:nth-child(2n) { | |
| 296 | background-color: #f8f8f8; | |
| 297 | } | |
| 298 | ||
| 299 | /* IMAGES | |
| 300 | =============================================================================*/ | |
| 301 | ||
| 302 | img { | |
| 303 | max-width: 100% | |
| 304 | } | |
| 1 | 305 |
| 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 | # | |
| 3 | # Filename Extensions | |
| 4 | # | |
| 5 | # ######################################################################## | |
| 6 | ||
| 7 | # Comma-separated list of filename extensions. | |
| 8 | Dialog.file.choose.filter.ext.markdown=*.Rmd,*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt | |
| 9 | Dialog.file.choose.filter.ext.definition=*.yml,*.yaml,*.properties,*.props | |
| 10 | Dialog.file.choose.filter.ext.xml=*.xml,*.Rxml | |
| 11 | Dialog.file.choose.filter.ext.all=*.* | |
| 12 | ||
| 13 | # ######################################################################## | |
| 14 | # | |
| 15 | # Variable Name Editor | |
| 16 | # | |
| 17 | # ######################################################################## | |
| 18 | ||
| 19 | # Maximum number of characters for a variable name. A variable is defined | |
| 20 | # as one or more non-whitespace characters up to this maximum length. | |
| 21 | editor.variable.maxLength=256 | |
| 22 | ||
| 23 | # ######################################################################## | |
| 24 | # | |
| 25 | # Dialog Preferences | |
| 26 | # | |
| 27 | # ######################################################################## | |
| 28 | ||
| 29 | # docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ButtonBar.html | |
| 30 | dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R | |
| 31 | dialog.alert.button.order.linux=L_HE+UNYACBXIO_R | |
| 32 | dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R | |
| 33 | ||
| 34 | # Ensures a consistent button order for alert dialogs across platforms (because | |
| 35 | # the default button order on Linux defies all logic). Power to the people. | |
| 36 | dialog.alert.button.order=${dialog.alert.button.order.windows} | |
| 1 | 37 |
| 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 |