Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
A .classpath
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>
121
A .gitattributes
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
117
A .gitignore
1
/bin/
2
/build/
3
/.gradle/
4
/gradle/
5
/.nb-gradle
6
/private
7
.nb-gradle-properties
8
scrivenvar.pro
19
A .project
1
<?xml version="1.0" encoding="UTF-8"?>
2
<projectDescription>
3
	<name>Markdown Writer FX</name>
4
	<comment></comment>
5
	<projects>
6
	</projects>
7
	<buildSpec>
8
		<buildCommand>
9
			<name>org.eclipse.jdt.core.javabuilder</name>
10
			<arguments>
11
			</arguments>
12
		</buildCommand>
13
		<buildCommand>
14
			<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
15
			<arguments>
16
			</arguments>
17
		</buildCommand>
18
	</buildSpec>
19
	<natures>
20
		<nature>org.eclipse.jdt.core.javanature</nature>
21
		<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
22
	</natures>
23
	<filteredResources>
24
		<filter>
25
			<id>1438449113801</id>
26
			<name></name>
27
			<type>26</type>
28
			<matcher>
29
				<id>org.eclipse.ui.ide.multiFilter</id>
30
				<arguments>1.0-projectRelativePath-matches-false-false-build</arguments>
31
			</matcher>
32
		</filter>
33
		<filter>
34
			<id>1438449113801</id>
35
			<name></name>
36
			<type>26</type>
37
			<matcher>
38
				<id>org.eclipse.ui.ide.multiFilter</id>
39
				<arguments>1.0-projectRelativePath-matches-false-false-.gradle</arguments>
40
			</matcher>
41
		</filter>
42
	</filteredResources>
43
</projectDescription>
144
A .settings/org.eclipse.buildship.core.prefs
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=\:
114
A .settings/org.eclipse.core.runtime.prefs
1
eclipse.preferences.version=1
2
line.separator=\n
13
A .settings/org.eclipse.jdt.core.prefs
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
18
A .settings/org.eclipse.jdt.ui.prefs
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
165
A .travis.yml
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
116
A CHANGES.md
1
# Change Log
2
3
## 0.8
4
5
- Load YAML variables from files
6
- Upgraded to Apache Commons Configuration 2.1
7
- Fixed bug with settings using comma-separated file extensions
8
9
## 0.7
10
11
- Added cursor to the preview pane
12
- Reconfigured constants to use settings
13
- Organized MainWindow code by similar method calls
14
- Added single entry point for refreshing file editor tab
15
16
## 0.6
17
18
- Revised synchronized scrolling with preview panel
19
- Added universal character encoding detection
20
- Removed options panel
21
- Decoupled Editor Tab and Preview Pane
22
23
## 0.5
24
25
- Added document processors for Markdown and Variables
26
- Simplified code base
27
- Added `Ctrl+Space` hot key for quick variable injection
28
- Replaced commonmark-java with flexmark
29
- Insert `CARETPOSITION` into document for preview pane scroll position reference
30
31
## 0.4
32
33
- Changed name to Scrivenvar
34
- Added hot-keys for variable mode and autocomplete
35
- Replaced pegdown with commonmark-java
36
- Started document processors to provide XSLT and variable dereferencing
37
38
## 0.3
39
40
- Changed name to Scrivendor
41
- Changed logo to match
42
- Started to implement service-oriented architecture
43
44
## 0.2
45
46
- RichTextFX (and dependencies) updated to version 0.6.10 (fixes bugs)
47
- pegdown Markdown parser updated to version 1.6
48
- Added five new pegdown 1.6 extension flags to Markdown Options tab
49
- Minor improvements
50
51
## 0.1
152
53
- Initial release
A CREDITS.md
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)
11
  * Shy Shalom, Kohei Taketa: [juniversalchardet](https://github.com/takscape/juniversalchardet)
112
A LICENSE
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.
125
A README.md
1
![Logo](images/logo64.png)
2
3
$application.title$
4
===
5
6
Word processing with variables.
7
8
![Screenshot](images/screenshot.png)
9
10
Features
11
---
12
13
* User-defined variables
14
* Recursive variable definitions
15
* Real-time document preview with variable substitution
16
* Platform independent (Windows, Linux, MacOS)
17
* Auto-insert variable names pressing `Control+Space`
18
19
Future Features
20
---
21
* Spell check
22
* Search and replace, with or without variables
23
* XML and XSL processing
24
* R integration using [Rserve](https://rforge.net/Rserve/)
25
* Re-organize variable names
26
27
Requirements
28
---
29
30
Download and install [Java 8u40](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
31
32
Installation
33
---
34
1. [Download](https://github.com/DaveJarvis/scrivenvar/releases) the latest zip archive and extract it to any folder.
35
1. Double-click `scrivenvar.jar` to start the application.
36
37
License
38
---
39
40
This software is licensed under the [BSD 2-Clause License](LICENSE).
141
A Scrivenvar.jfdproj
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>
121
A USAGE.md
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|
131
A build.gradle
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: 'org.ahocorasick', name: 'ahocorasick', version: '0.3.0'
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: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.4'
27
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.4'
28
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.8.4'
29
  compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.8.4'
30
  compile group: 'org.yaml', name: 'snakeyaml', version: '1.17'
31
  compile group: 'com.googlecode.juniversalchardet', name: 'juniversalchardet', version: '1.0.3'
32
  compile group: 'org.apache.commons', name: 'commons-configuration2', version: '2.1'
33
}
34
35
jar {
36
	baseName = 'scrivenvar'
37
  
38
  from {
39
    (configurations.runtime).collect {
40
      it.isDirectory() ? it : zipTree(it)
41
    }
42
  }
43
    
44
	manifest {
45
		attributes 'Main-Class': mainClassName,
46
					'Class-Path': configurations.compile.collect { 'lib/' + it.getName() }.join(' ')
47
	}
48
}
49
50
distributions {
51
	main {
52
		baseName = 'scrivenvar'
53
		contents {
54
			from { ['LICENSE', 'README.md'] }
55
			into( 'images' ) {
56
				from { 'images' }
57
			}
58
		}
59
	}
60
}
161
A gradle.properties
11
A images/logo64.png
Binary file
A images/screenshot.png
Binary file
A scrivenvar.yaml
1
application:
2
  title: Scrivenvar
3
14
A settings.gradle
11
A src/main/java/com/scrivenvar/AbstractPane.java
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.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
}
152
A src/main/java/com/scrivenvar/Constants.java
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.service.Settings;
31
32
/**
33
 * @author White Magic Software, Ltd.
34
 */
35
public class Constants {
36
37
  private static final Settings SETTINGS = Services.load( Settings.class );
38
39
  /**
40
   * Prevent instantiation.
41
   */
42
  private Constants() {
43
  }
44
45
  private static String get( final String key ) {
46
    return SETTINGS.getSetting( key, "" );
47
  }
48
49
  // Bootstrapping...
50
  public static final String SETTINGS_NAME = "/com/scrivenvar/settings.properties";
51
52
  public static final String APP_BUNDLE_NAME = get( "application.messages" );
53
54
  public static final String STYLESHEET_SCENE = get( "file.stylesheet.scene" );
55
  public static final String STYLESHEET_MARKDOWN = get( "file.stylesheet.markdown" );
56
  public static final String STYLESHEET_PREVIEW = get( "file.stylesheet.preview" );
57
58
  public static final String FILE_LOGO_16 = get( "file.logo.16" );
59
  public static final String FILE_LOGO_32 = get( "file.logo.32" );
60
  public static final String FILE_LOGO_128 = get( "file.logo.128" );
61
  public static final String FILE_LOGO_256 = get( "file.logo.256" );
62
  public static final String FILE_LOGO_512 = get( "file.logo.512" );
63
64
  public static final String CARET_POSITION_BASE = get( "caret.token.base" );
65
  public static final String CARET_POSITION_MD = get( "caret.token.markdown" );
66
  public static final String CARET_POSITION_XML = get( "caret.token.xml" );
67
  public static final String CARET_POSITION_HTML = get( "caret.token.html" );
68
69
  public static final String PREFS_ROOT = get( "preferences.root" );
70
  public static final String PREFS_STATE = get( "preferences.root.state" );
71
  public static final String PREFS_OPTIONS = get( "preferences.root.options" );
72
  public static final String PREFS_DEFINITION_SOURCE = get( "preferences.root.definition.source" );
73
}
174
A src/main/java/com/scrivenvar/FileEditorTab.java
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.editors.EditorPane;
29
import com.scrivenvar.editors.markdown.MarkdownEditorPane;
30
import com.scrivenvar.service.Options;
31
import com.scrivenvar.service.events.AlertMessage;
32
import com.scrivenvar.service.events.AlertService;
33
import java.nio.charset.Charset;
34
import java.nio.file.Files;
35
import java.nio.file.Path;
36
import static java.util.Locale.ENGLISH;
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.beans.value.ChangeListener;
45
import javafx.event.Event;
46
import javafx.scene.Node;
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.undo.UndoManager;
52
import org.fxmisc.wellbehaved.event.EventPattern;
53
import org.fxmisc.wellbehaved.event.InputMap;
54
import org.mozilla.universalchardet.UniversalDetector;
55
56
/**
57
 * Editor for a single file.
58
 *
59
 * @author Karl Tauber and White Magic Software, Ltd.
60
 */
61
public final class FileEditorTab extends Tab {
62
63
  private final Options options = Services.load( Options.class );
64
  private final AlertService alertService = Services.load( AlertService.class );
65
66
  private EditorPane editorPane;
67
68
  /**
69
   * Character encoding used by the file (or default encoding if none found).
70
   */
71
  private Charset encoding;
72
73
  private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper();
74
  private final BooleanProperty canUndo = new SimpleBooleanProperty();
75
  private final BooleanProperty canRedo = new SimpleBooleanProperty();
76
  private Path path;
77
78
  FileEditorTab( final Path path ) {
79
    setPath( path );
80
81
    this.modified.addListener( (observable, oldPath, newPath) -> updateTab() );
82
    updateTab();
83
84
    setOnSelectionChanged( e -> {
85
      if( isSelected() ) {
86
        Platform.runLater( () -> activated() );
87
      }
88
    } );
89
  }
90
91
  private void updateTab() {
92
    setText( getTabTitle() );
93
    setGraphic( getModifiedMark() );
94
    setTooltip( getTabTooltip() );
95
  }
96
97
  /**
98
   * Returns the base filename (without the directory names).
99
   *
100
   * @return The untitled text if the path hasn't been set.
101
   */
102
  private String getTabTitle() {
103
    final Path filePath = getPath();
104
105
    return (filePath == null)
106
      ? Messages.get( "FileEditor.untitled" )
107
      : filePath.getFileName().toString();
108
  }
109
110
  /**
111
   * Returns the full filename represented by the path.
112
   *
113
   * @return The untitled text if the path hasn't been set.
114
   */
115
  private Tooltip getTabTooltip() {
116
    final Path filePath = getPath();
117
118
    return (filePath == null)
119
      ? null
120
      : new Tooltip( filePath.toString() );
121
  }
122
123
  /**
124
   * Returns a marker to indicate whether the file has been modified.
125
   *
126
   * @return "*" when the file has changed; otherwise null.
127
   */
128
  private Text getModifiedMark() {
129
    return isModified() ? new Text( "*" ) : null;
130
  }
131
132
  /**
133
   * Called when the user switches tab.
134
   */
135
  private void activated() {
136
    // Tab is closed or no longer active.
137
    if( getTabPane() == null || !isSelected() ) {
138
      return;
139
    }
140
141
    // Switch to the tab without loading if the contents are already in memory.
142
    if( getContent() != null ) {
143
      getEditorPane().requestFocus();
144
      return;
145
    }
146
147
    // Load the text and update the preview before the undo manager.
148
    load();
149
150
    // Track undo requests -- can only be called *after* load.
151
    initUndoManager();
152
    initLayout();
153
    initFocus();
154
  }
155
156
  private void initLayout() {
157
    setContent( getScrollPane() );
158
  }
159
160
  private Node getScrollPane() {
161
    return getEditorPane().getScrollPane();
162
  }
163
164
  private void initFocus() {
165
    getEditorPane().requestFocus();
166
  }
167
168
  private void initUndoManager() {
169
    final UndoManager undoManager = getUndoManager();
170
171
    // Clear undo history after first load.
172
    undoManager.forgetHistory();
173
174
    // Bind the editor undo manager to the properties.
175
    modified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) );
176
    canUndo.bind( undoManager.undoAvailableProperty() );
177
    canRedo.bind( undoManager.redoAvailableProperty() );
178
  }
179
180
  /**
181
   * Returns the index into the text where the caret blinks happily away.
182
   *
183
   * @return A number from 0 to the editor's document text length.
184
   */
185
  public int getCaretPosition() {
186
    return getEditorPane().getEditor().getCaretPosition();
187
  }
188
189
  /**
190
   * Returns true if the given path exactly matches this tab's path.
191
   *
192
   * @param check The path to compare against.
193
   *
194
   * @return true The paths are the same.
195
   */
196
  public boolean isPath( final Path check ) {
197
    final Path filePath = getPath();
198
199
    return filePath == null ? false : filePath.equals( check );
200
  }
201
202
  /**
203
   * Reads the entire file contents from the path associated with this tab.
204
   */
205
  private void load() {
206
    final Path filePath = getPath();
207
208
    if( filePath != null ) {
209
      try {
210
        getEditorPane().setText( asString( Files.readAllBytes( filePath ) ) );
211
      } catch( Exception ex ) {
212
        alert(
213
          "FileEditor.loadFailed.title", "FileEditor.loadFailed.message", ex
214
        );
215
      }
216
    }
217
  }
218
219
  /**
220
   * Saves the entire file contents from the path associated with this tab.
221
   *
222
   * @return true The file has been saved.
223
   */
224
  public boolean save() {
225
    try {
226
      Files.write( getPath(), asBytes( getEditorPane().getText() ) );
227
      getEditorPane().getUndoManager().mark();
228
      return true;
229
    } catch( Exception ex ) {
230
      return alert(
231
        "FileEditor.saveFailed.title", "FileEditor.saveFailed.message", ex
232
      );
233
    }
234
  }
235
236
  /**
237
   * Creates an alert dialog and waits for it to close.
238
   *
239
   * @param titleKey Resource bundle key for the alert dialog title.
240
   * @param messageKey Resource bundle key for the alert dialog message.
241
   * @param e The unexpected happening.
242
   *
243
   * @return false
244
   */
245
  private boolean alert(
246
    final String titleKey, final String messageKey, final Exception e ) {
247
    final AlertService service = getAlertService();
248
249
    final AlertMessage message = service.createAlertMessage(
250
      Messages.get( titleKey ),
251
      Messages.get( messageKey ),
252
      getPath(),
253
      e.getMessage()
254
    );
255
256
    service.createAlertError( message ).showAndWait();
257
    return false;
258
  }
259
260
  /**
261
   * Returns a best guess at the file encoding. If the encoding could not be
262
   * detected, this will return the default charset for the JVM.
263
   *
264
   * @param bytes The bytes to perform character encoding detection.
265
   *
266
   * @return The character encoding.
267
   */
268
  private Charset detectEncoding( final byte[] bytes ) {
269
    final UniversalDetector detector = new UniversalDetector( null );
270
    detector.handleData( bytes, 0, bytes.length );
271
    detector.dataEnd();
272
273
    final String charset = detector.getDetectedCharset();
274
    final Charset charEncoding = charset == null
275
      ? Charset.defaultCharset()
276
      : Charset.forName( charset.toUpperCase( ENGLISH ) );
277
278
    detector.reset();
279
280
    return charEncoding;
281
  }
282
283
  /**
284
   * Converts the given string to an array of bytes using the encoding that was
285
   * originally detected (if any) and associated with this file.
286
   *
287
   * @param text The text to convert into the original file encoding.
288
   *
289
   * @return A series of bytes ready for writing to a file.
290
   */
291
  private byte[] asBytes( final String text ) {
292
    return text.getBytes( getEncoding() );
293
  }
294
295
  /**
296
   * Converts the given bytes into a Java String. This will call setEncoding
297
   * with the encoding detected by the CharsetDetector.
298
   *
299
   * @param text The text of unknown character encoding.
300
   *
301
   * @return The text, in its auto-detected encoding, as a String.
302
   */
303
  private String asString( final byte[] text ) {
304
    setEncoding( detectEncoding( text ) );
305
    return new String( text, getEncoding() );
306
  }
307
308
  Path getPath() {
309
    return this.path;
310
  }
311
312
  void setPath( final Path path ) {
313
    this.path = path;
314
  }
315
316
  public boolean isModified() {
317
    return this.modified.get();
318
  }
319
320
  ReadOnlyBooleanProperty modifiedProperty() {
321
    return this.modified.getReadOnlyProperty();
322
  }
323
324
  BooleanProperty canUndoProperty() {
325
    return this.canUndo;
326
  }
327
328
  BooleanProperty canRedoProperty() {
329
    return this.canRedo;
330
  }
331
332
  private UndoManager getUndoManager() {
333
    return getEditorPane().getUndoManager();
334
  }
335
336
  /**
337
   * Forwards the request to the editor pane.
338
   *
339
   * @param <T> The type of event listener to add.
340
   * @param <U> The type of consumer to add.
341
   * @param event The event that should trigger updates to the listener.
342
   * @param consumer The listener to receive update events.
343
   */
344
  public <T extends Event, U extends T> void addEventListener(
345
    final EventPattern<? super T, ? extends U> event,
346
    final Consumer<? super U> consumer ) {
347
    getEditorPane().addEventListener( event, consumer );
348
  }
349
350
  /**
351
   * Forwards to the editor pane's listeners for keyboard events.
352
   *
353
   * @param map The new input map to replace the existing keyboard listener.
354
   */
355
  public void addEventListener( final InputMap<InputEvent> map ) {
356
    getEditorPane().addEventListener( map );
357
  }
358
359
  /**
360
   * Forwards to the editor pane's listeners for keyboard events.
361
   *
362
   * @param map The existing input map to remove from the keyboard listeners.
363
   */
364
  public void removeEventListener( final InputMap<InputEvent> map ) {
365
    getEditorPane().removeEventListener( map );
366
  }
367
368
  /**
369
   * Forwards to the editor pane's listeners for text change events.
370
   *
371
   * @param listener The listener to notify when the text changes.
372
   */
373
  public void addTextChangeListener( final ChangeListener<String> listener ) {
374
    getEditorPane().addTextChangeListener( listener );
375
  }
376
377
  /**
378
   * Forwards to the editor pane's listeners for caret paragraph change events.
379
   *
380
   * @param listener The listener to notify when the caret changes paragraphs.
381
   */
382
  public void addCaretParagraphListener( final ChangeListener<Integer> listener ) {
383
    getEditorPane().addCaretParagraphListener( listener );
384
  }
385
386
  /**
387
   * Forwards the request to the editor pane.
388
   *
389
   * @return The text to process.
390
   */
391
  public String getEditorText() {
392
    return getEditorPane().getText();
393
  }
394
395
  /**
396
   * Returns the editor pane, or creates one if it doesn't yet exist.
397
   *
398
   * @return The editor pane, never null.
399
   */
400
  protected EditorPane getEditorPane() {
401
    if( this.editorPane == null ) {
402
      this.editorPane = new MarkdownEditorPane();
403
    }
404
405
    return this.editorPane;
406
  }
407
408
  private AlertService getAlertService() {
409
    return this.alertService;
410
  }
411
412
  private Options getOptions() {
413
    return this.options;
414
  }
415
416
  private Charset getEncoding() {
417
    return this.encoding;
418
  }
419
420
  private void setEncoding( final Charset encoding ) {
421
    this.encoding = encoding;
422
  }
423
424
  /**
425
   * Returns the tab title, without any modified indicators.
426
   *
427
   * @return The tab title.
428
   */
429
  @Override
430
  public String toString() {
431
    return getTabTitle();
432
  }
433
}
1434
A src/main/java/com/scrivenvar/FileEditorTabPane.java
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 final class FileEditorTabPane extends TabPane {
75
76
  private final static String FILTER_EXTENSIONS = "filter.file";
77
  private final static String FILTER_EXTENSION_TITLES = "Dialog.file.choose.filter";
78
79
  private final Options options = Services.load( Options.class );
80
  private final Settings settings = Services.load( Settings.class );
81
  private final AlertService alertService = Services.load( AlertService.class );
82
83
  private final ReadOnlyObjectWrapper<Path> openDefinition = new ReadOnlyObjectWrapper<>();
84
  private final ReadOnlyObjectWrapper<FileEditorTab> activeFileEditor = new ReadOnlyObjectWrapper<>();
85
  private final ReadOnlyBooleanWrapper anyFileEditorModified = new ReadOnlyBooleanWrapper();
86
87
  /**
88
   * Constructs a new file editor tab pane.
89
   */
90
  public FileEditorTabPane() {
91
    final ObservableList<Tab> tabs = getTabs();
92
93
    setFocusTraversable( false );
94
    setTabClosingPolicy( TabClosingPolicy.ALL_TABS );
95
96
    addTabSelectionListener(
97
      (ObservableValue<? extends Tab> tabPane,
98
        final Tab oldTab, final Tab newTab) -> {
99
100
        if( newTab != null ) {
101
          activeFileEditor.set( (FileEditorTab)newTab );
102
        }
103
      }
104
    );
105
106
    final ChangeListener<Boolean> modifiedListener = (observable, oldValue, newValue) -> {
107
      for( final Tab tab : tabs ) {
108
        if( ((FileEditorTab)tab).isModified() ) {
109
          this.anyFileEditorModified.set( true );
110
          break;
111
        }
112
      }
113
    };
114
115
    tabs.addListener(
116
      (ListChangeListener<Tab>)change -> {
117
        while( change.next() ) {
118
          if( change.wasAdded() ) {
119
            change.getAddedSubList().stream().forEach( (tab) -> {
120
              ((FileEditorTab)tab).modifiedProperty().addListener( modifiedListener );
121
            } );
122
          } else if( change.wasRemoved() ) {
123
            change.getRemoved().stream().forEach( (tab) -> {
124
              ((FileEditorTab)tab).modifiedProperty().removeListener( modifiedListener );
125
            } );
126
          }
127
        }
128
129
        // Changes in the tabs may also change anyFileEditorModified property
130
        // (e.g. closed modified file)
131
        modifiedListener.changed( null, null, null );
132
      }
133
    );
134
  }
135
136
  /**
137
   * Delegates to the active file editor.
138
   *
139
   * @param <T> Event type.
140
   * @param <U> Consumer type.
141
   * @param event Event to pass to the editor.
142
   * @param consumer Consumer to pass to the editor.
143
   */
144
  public <T extends Event, U extends T> void addEventListener(
145
    final EventPattern<? super T, ? extends U> event,
146
    final Consumer<? super U> consumer ) {
147
    getActiveFileEditor().addEventListener( event, consumer );
148
  }
149
150
  /**
151
   * Delegates to the active file editor pane, and, ultimately, to its text
152
   * area.
153
   *
154
   * @param map The map of methods to events.
155
   */
156
  public void addEventListener( final InputMap<InputEvent> map ) {
157
    getActiveFileEditor().addEventListener( map );
158
  }
159
160
  /**
161
   * Remove a keyboard event listener from the active file editor.
162
   *
163
   * @param map The keyboard events to remove.
164
   */
165
  public void removeEventListener( final InputMap<InputEvent> map ) {
166
    getActiveFileEditor().removeEventListener( map );
167
  }
168
169
  /**
170
   * Allows observers to be notified when the current file editor tab changes.
171
   *
172
   * @param listener The listener to notify of tab change events.
173
   */
174
  public void addTabSelectionListener( final ChangeListener<Tab> listener ) {
175
    // Observe the tab so that when a new tab is opened or selected,
176
    // a notification is kicked off.
177
    getSelectionModel().selectedItemProperty().addListener( listener );
178
  }
179
180
  /**
181
   * Allows clients to manipulate the editor content directly.
182
   *
183
   * @return The text area for the active file editor.
184
   */
185
  public StyledTextArea getEditor() {
186
    return getActiveFileEditor().getEditorPane().getEditor();
187
  }
188
189
  public FileEditorTab getActiveFileEditor() {
190
    return this.activeFileEditor.get();
191
  }
192
193
  public ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() {
194
    return this.activeFileEditor.getReadOnlyProperty();
195
  }
196
197
  ReadOnlyBooleanProperty anyFileEditorModifiedProperty() {
198
    return this.anyFileEditorModified.getReadOnlyProperty();
199
  }
200
201
  private FileEditorTab createFileEditor( final Path path ) {
202
    final FileEditorTab tab = new FileEditorTab( path );
203
204
    tab.setOnCloseRequest( e -> {
205
      if( !canCloseEditor( tab ) ) {
206
        e.consume();
207
      }
208
    } );
209
210
    return tab;
211
  }
212
213
  /**
214
   * Called when the user selects New from the File menu.
215
   *
216
   * @return The newly added tab.
217
   */
218
  void newEditor() {
219
    final FileEditorTab tab = createFileEditor( null );
220
221
    getTabs().add( tab );
222
    getSelectionModel().select( tab );
223
  }
224
225
  void openFileDialog() {
226
    final String title = get( "Dialog.file.choose.open.title" );
227
    final FileChooser dialog = createFileChooser( title );
228
    final List<File> files = dialog.showOpenMultipleDialog( getWindow() );
229
230
    if( files != null ) {
231
      openFiles( files );
232
    }
233
  }
234
235
  /**
236
   * Opens the files into new editors, unless one of those files was a
237
   * definition file. The definition file is loaded into the definition pane,
238
   * but only the first one selected (multiple definition files will result in a
239
   * warning).
240
   *
241
   * @param files The list of non-definition files that the were requested to
242
   * open.
243
   *
244
   * @return A list of files that can be opened in text editors.
245
   */
246
  private void openFiles( final List<File> files ) {
247
    final FileTypePredicate predicate
248
      = new FileTypePredicate( createExtensionFilter( "definition" ).getExtensions() );
249
250
    // The user might have opened multiple definitions files. These will
251
    // be discarded from the text editable files.
252
    final List<File> definitions
253
      = files.stream().filter( predicate ).collect( Collectors.toList() );
254
255
    // Create a modifiable list to remove any definition files that were
256
    // opened.
257
    final List<File> editors = new ArrayList<>( files );
258
259
    if( editors.size() > 0 ) {
260
      saveLastDirectory( editors.get( 0 ) );
261
    }
262
263
    editors.removeAll( definitions );
264
265
    // Open editor-friendly files (e.g,. Markdown, XML) in new tabs.
266
    if( editors.size() > 0 ) {
267
      openEditors( editors, 0 );
268
    }
269
270
    if( definitions.size() > 0 ) {
271
      openDefinition( definitions.get( 0 ) );
272
    }
273
  }
274
275
  private void openEditors( final List<File> files, final int activeIndex ) {
276
    final int fileTally = files.size();
277
    final List<Tab> tabs = getTabs();
278
279
    // Close single unmodified "Untitled" tab.
280
    if( tabs.size() == 1 ) {
281
      final FileEditorTab fileEditor = (FileEditorTab)(tabs.get( 0 ));
282
283
      if( fileEditor.getPath() == null && !fileEditor.isModified() ) {
284
        closeEditor( fileEditor, false );
285
      }
286
    }
287
288
    for( int i = 0; i < fileTally; i++ ) {
289
      final Path path = files.get( i ).toPath();
290
291
      FileEditorTab fileEditorTab = findEditor( path );
292
293
      // Only open new files.
294
      if( fileEditorTab == null ) {
295
        fileEditorTab = createFileEditor( path );
296
        getTabs().add( fileEditorTab );
297
      }
298
299
      // Select the first file in the list.
300
      if( i == activeIndex ) {
301
        getSelectionModel().select( fileEditorTab );
302
      }
303
    }
304
  }
305
306
  /**
307
   * Returns a property that changes when a new definition file is opened.
308
   *
309
   * @return The path to a definition file that was opened.
310
   */
311
  public ReadOnlyObjectProperty<Path> onOpenDefinitionFileProperty() {
312
    return getOnOpenDefinitionFile().getReadOnlyProperty();
313
  }
314
315
  private ReadOnlyObjectWrapper<Path> getOnOpenDefinitionFile() {
316
    return this.openDefinition;
317
  }
318
319
  /**
320
   * Called when the user has opened a definition file (using the file open
321
   * dialog box). This will replace the current set of definitions for the
322
   * active tab.
323
   *
324
   * @param definition The file to open.
325
   */
326
  private void openDefinition( final File definition ) {
327
    // TODO: Prevent reading this file twice when a new text document is opened.
328
    // (might be a matter of checking the value first).
329
    getOnOpenDefinitionFile().set( definition.toPath() );
330
  }
331
332
  boolean saveEditor( final FileEditorTab fileEditor ) {
333
    if( fileEditor == null || !fileEditor.isModified() ) {
334
      return true;
335
    }
336
337
    if( fileEditor.getPath() == null ) {
338
      getSelectionModel().select( fileEditor );
339
340
      final FileChooser fileChooser = createFileChooser( Messages.get( "Dialog.file.choose.save.title" ) );
341
      final File file = fileChooser.showSaveDialog( getWindow() );
342
      if( file == null ) {
343
        return false;
344
      }
345
346
      saveLastDirectory( file );
347
      fileEditor.setPath( file.toPath() );
348
    }
349
350
    return fileEditor.save();
351
  }
352
353
  boolean saveAllEditors() {
354
    boolean success = true;
355
356
    for( FileEditorTab fileEditor : getAllEditors() ) {
357
      if( !saveEditor( fileEditor ) ) {
358
        success = false;
359
      }
360
    }
361
362
    return success;
363
  }
364
365
  /**
366
   * Answers whether the file has had modifications. '
367
   *
368
   * @param tab THe tab to check for modifications.
369
   *
370
   * @return false The file is unmodified.
371
   */
372
  boolean canCloseEditor( final FileEditorTab tab ) {
373
    if( !tab.isModified() ) {
374
      return true;
375
    }
376
377
    final AlertMessage message = getAlertService().createAlertMessage(
378
      Messages.get( "Alert.file.close.title" ),
379
      Messages.get( "Alert.file.close.text" ),
380
      tab.getText()
381
    );
382
383
    final Alert alert = getAlertService().createAlertConfirmation( message );
384
    final ButtonType response = alert.showAndWait().get();
385
386
    return response == YES ? saveEditor( tab ) : response == NO;
387
  }
388
389
  private AlertService getAlertService() {
390
    return this.alertService;
391
  }
392
393
  boolean closeEditor( FileEditorTab fileEditor, boolean save ) {
394
    if( fileEditor == null ) {
395
      return true;
396
    }
397
398
    final Tab tab = fileEditor;
399
400
    if( save ) {
401
      Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT );
402
      Event.fireEvent( tab, event );
403
404
      if( event.isConsumed() ) {
405
        return false;
406
      }
407
    }
408
409
    getTabs().remove( tab );
410
411
    if( tab.getOnClosed() != null ) {
412
      Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) );
413
    }
414
415
    return true;
416
  }
417
418
  boolean closeAllEditors() {
419
    final FileEditorTab[] allEditors = getAllEditors();
420
    final FileEditorTab activeEditor = getActiveFileEditor();
421
422
    // try to save active tab first because in case the user decides to cancel,
423
    // then it stays active
424
    if( activeEditor != null && !canCloseEditor( activeEditor ) ) {
425
      return false;
426
    }
427
428
    // This should be called any time a tab changes.
429
    persistPreferences();
430
431
    // save modified tabs
432
    for( int i = 0; i < allEditors.length; i++ ) {
433
      final FileEditorTab fileEditor = allEditors[ i ];
434
435
      if( fileEditor == activeEditor ) {
436
        continue;
437
      }
438
439
      if( fileEditor.isModified() ) {
440
        // activate the modified tab to make its modified content visible to the user
441
        getSelectionModel().select( i );
442
443
        if( !canCloseEditor( fileEditor ) ) {
444
          return false;
445
        }
446
      }
447
    }
448
449
    // Close all tabs.
450
    for( final FileEditorTab fileEditor : allEditors ) {
451
      if( !closeEditor( fileEditor, false ) ) {
452
        return false;
453
      }
454
    }
455
456
    return getTabs().isEmpty();
457
  }
458
459
  private FileEditorTab[] getAllEditors() {
460
    final ObservableList<Tab> tabs = getTabs();
461
    final int length = tabs.size();
462
    final FileEditorTab[] allEditors = new FileEditorTab[ length ];
463
464
    for( int i = 0; i < length; i++ ) {
465
      allEditors[ i ] = (FileEditorTab)tabs.get( i );
466
    }
467
468
    return allEditors;
469
  }
470
471
  /**
472
   * Returns the file editor tab that has the given path.
473
   *
474
   * @return null No file editor tab for the given path was found.
475
   */
476
  private FileEditorTab findEditor( final Path path ) {
477
    for( final Tab tab : getTabs() ) {
478
      final FileEditorTab fileEditor = (FileEditorTab)tab;
479
480
      if( fileEditor.isPath( path ) ) {
481
        return fileEditor;
482
      }
483
    }
484
485
    return null;
486
  }
487
488
  private FileChooser createFileChooser( String title ) {
489
    final FileChooser fileChooser = new FileChooser();
490
491
    fileChooser.setTitle( title );
492
    fileChooser.getExtensionFilters().addAll(
493
      createExtensionFilters() );
494
495
    final String lastDirectory = getPreferences().get( "lastDirectory", null );
496
    File file = new File( (lastDirectory != null) ? lastDirectory : "." );
497
498
    if( !file.isDirectory() ) {
499
      file = new File( "." );
500
    }
501
502
    fileChooser.setInitialDirectory( file );
503
    return fileChooser;
504
  }
505
506
  private List<ExtensionFilter> createExtensionFilters() {
507
    final List<ExtensionFilter> list = new ArrayList<>();
508
509
    // TODO: Return a list of all properties that match the filter prefix.
510
    // This will allow dynamic filters to be added and removed just by
511
    // updating the properties file.
512
    list.add( createExtensionFilter( "definition" ) );
513
    list.add( createExtensionFilter( "markdown" ) );
514
    list.add( createExtensionFilter( "xml" ) );
515
    list.add( createExtensionFilter( "all" ) );
516
    return list;
517
  }
518
519
  private ExtensionFilter createExtensionFilter( final String filetype ) {
520
    final String tKey = String.format( "%s.title.%s", FILTER_EXTENSION_TITLES, filetype );
521
    final String eKey = String.format( "%s.ext.%s", FILTER_EXTENSIONS, filetype );
522
523
    return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) );
524
  }
525
526
  private List<String> getExtensions( final String key ) {
527
    return getSettings().getStringSettingList( key );
528
  }
529
530
  private void saveLastDirectory( final File file ) {
531
    getPreferences().put( "lastDirectory", file.getParent() );
532
  }
533
534
  public void restorePreferences() {
535
    int activeIndex = 0;
536
537
    final Preferences preferences = getPreferences();
538
    final String[] fileNames = Utils.getPrefsStrings( preferences, "file" );
539
    final String activeFileName = preferences.get( "activeFile", null );
540
541
    final ArrayList<File> files = new ArrayList<>( fileNames.length );
542
543
    for( final String fileName : fileNames ) {
544
      final File file = new File( fileName );
545
546
      if( file.exists() ) {
547
        files.add( file );
548
549
        if( fileName.equals( activeFileName ) ) {
550
          activeIndex = files.size() - 1;
551
        }
552
      }
553
    }
554
555
    if( files.isEmpty() ) {
556
      newEditor();
557
    } else {
558
      openEditors( files, activeIndex );
559
    }
560
  }
561
562
  public void persistPreferences() {
563
    final ObservableList<Tab> allEditors = getTabs();
564
    final List<String> fileNames = new ArrayList<>( allEditors.size() );
565
566
    for( final Tab tab : allEditors ) {
567
      final FileEditorTab fileEditor = (FileEditorTab)tab;
568
      final Path filePath = fileEditor.getPath();
569
570
      if( filePath != null ) {
571
        fileNames.add( filePath.toString() );
572
      }
573
    }
574
575
    final Preferences preferences = getPreferences();
576
    Utils.putPrefsStrings( preferences, "file", fileNames.toArray( new String[ fileNames.size() ] ) );
577
578
    final FileEditorTab activeEditor = getActiveFileEditor();
579
    final Path filePath = activeEditor == null ? null : activeEditor.getPath();
580
581
    if( filePath == null ) {
582
      preferences.remove( "activeFile" );
583
    } else {
584
      preferences.put( "activeFile", filePath.toString() );
585
    }
586
  }
587
588
  private Settings getSettings() {
589
    return this.settings;
590
  }
591
592
  protected Options getOptions() {
593
    return this.options;
594
  }
595
596
  private Window getWindow() {
597
    return getScene().getWindow();
598
  }
599
600
  private Preferences getPreferences() {
601
    return getOptions().getState();
602
  }
603
604
  Node getNode() {
605
    return this;
606
  }
607
}
1608
A src/main/java/com/scrivenvar/Main.java
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.*;
31
import com.scrivenvar.service.Options;
32
import com.scrivenvar.service.events.AlertService;
33
import com.scrivenvar.util.StageState;
34
import javafx.application.Application;
35
import javafx.scene.Scene;
36
import javafx.scene.image.Image;
37
import javafx.stage.Stage;
38
39
/**
40
 * Main application entry point. The application allows users to edit Markdown
41
 * files and see a real-time preview of the edits.
42
 *
43
 * @author Karl Tauber and White Magic Software, Ltd.
44
 */
45
public final class Main extends Application {
46
47
  private static Application app;
48
49
  private final MainWindow mainWindow = new MainWindow();
50
  private final Options options = Services.load( Options.class );
51
52
  public static void main( String[] args ) {
53
    launch( args );
54
  }
55
56
  /**
57
   * Application entry point.
58
   *
59
   * @param stage The primary application stage.
60
   *
61
   * @throws Exception Could not read configuration file.
62
   */
63
  @Override
64
  public void start( final Stage stage ) throws Exception {
65
    initApplication();
66
    initState( stage );
67
    initStage( stage );
68
    initAlertService();
69
70
    stage.show();
71
  }
72
73
  private void initApplication() {
74
    app = this;
75
  }
76
77
  private Options getOptions() {
78
    return this.options;
79
  }
80
81
  private String getApplicationTitle() {
82
    return Messages.get( "Main.title" );
83
  }
84
85
  private StageState initState( Stage stage ) {
86
    return new StageState( stage, getOptions().getState() );
87
  }
88
89
  private void initStage( Stage stage ) {
90
    stage.getIcons().addAll(
91
      createImage( FILE_LOGO_16 ),
92
      createImage( FILE_LOGO_32 ),
93
      createImage( FILE_LOGO_128 ),
94
      createImage( FILE_LOGO_256 ),
95
      createImage( FILE_LOGO_512 ) );
96
    stage.setTitle( getApplicationTitle() );
97
    stage.setScene( getScene() );
98
  }
99
100
  private void initAlertService() {
101
    final AlertService service = Services.load( AlertService.class );
102
    service.setWindow( getScene().getWindow() );
103
  }
104
105
  private Scene getScene() {
106
    return getMainWindow().getScene();
107
  }
108
109
  private MainWindow getMainWindow() {
110
    return this.mainWindow;
111
  }
112
113
  private static Application getApplication() {
114
    return app;
115
  }
116
117
  public static void showDocument( String uri ) {
118
    getApplication().getHostServices().showDocument( uri );
119
  }
120
121
  private Image createImage( final String filename ) {
122
    return new Image( filename );
123
  }
124
}
1125
A src/main/java/com/scrivenvar/MainWindow.java
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.FILE_LOGO_32;
31
import static com.scrivenvar.Constants.PREFS_DEFINITION_SOURCE;
32
import static com.scrivenvar.Constants.STYLESHEET_SCENE;
33
import static com.scrivenvar.Messages.get;
34
import com.scrivenvar.definition.DefinitionFactory;
35
import com.scrivenvar.definition.DefinitionPane;
36
import com.scrivenvar.definition.DefinitionSource;
37
import com.scrivenvar.definition.EmptyDefinitionSource;
38
import com.scrivenvar.editors.VariableNameInjector;
39
import com.scrivenvar.editors.markdown.MarkdownEditorPane;
40
import com.scrivenvar.preview.HTMLPreviewPane;
41
import com.scrivenvar.processors.HTMLPreviewProcessor;
42
import com.scrivenvar.processors.MarkdownCaretInsertionProcessor;
43
import com.scrivenvar.processors.MarkdownCaretReplacementProcessor;
44
import com.scrivenvar.processors.MarkdownProcessor;
45
import com.scrivenvar.processors.Processor;
46
import com.scrivenvar.processors.VariableProcessor;
47
import com.scrivenvar.service.Options;
48
import com.scrivenvar.util.Action;
49
import com.scrivenvar.util.ActionUtils;
50
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_DEFINITION;
51
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_EDITOR;
52
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_PREVIEW;
53
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.BOLD;
54
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.CODE;
55
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_ALT;
56
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_CODE_ALT;
57
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FLOPPY_ALT;
58
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FOLDER_OPEN_ALT;
59
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.HEADER;
60
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.ITALIC;
61
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LINK;
62
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_OL;
63
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_UL;
64
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.PICTURE_ALT;
65
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.QUOTE_LEFT;
66
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.REPEAT;
67
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.STRIKETHROUGH;
68
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.UNDO;
69
import java.net.MalformedURLException;
70
import java.nio.file.Path;
71
import java.util.Map;
72
import java.util.function.Function;
73
import java.util.prefs.Preferences;
74
import javafx.beans.binding.Bindings;
75
import javafx.beans.binding.BooleanBinding;
76
import javafx.beans.property.BooleanProperty;
77
import javafx.beans.property.SimpleBooleanProperty;
78
import javafx.beans.value.ObservableBooleanValue;
79
import javafx.beans.value.ObservableValue;
80
import javafx.collections.ListChangeListener.Change;
81
import javafx.collections.ObservableList;
82
import javafx.event.Event;
83
import javafx.scene.Node;
84
import javafx.scene.Scene;
85
import javafx.scene.control.Alert;
86
import javafx.scene.control.Alert.AlertType;
87
import javafx.scene.control.Menu;
88
import javafx.scene.control.MenuBar;
89
import javafx.scene.control.SplitPane;
90
import javafx.scene.control.Tab;
91
import javafx.scene.control.ToolBar;
92
import javafx.scene.control.TreeView;
93
import javafx.scene.image.Image;
94
import javafx.scene.image.ImageView;
95
import static javafx.scene.input.KeyCode.ESCAPE;
96
import javafx.scene.input.KeyEvent;
97
import static javafx.scene.input.KeyEvent.CHAR_UNDEFINED;
98
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
99
import javafx.scene.layout.BorderPane;
100
import javafx.scene.layout.VBox;
101
import javafx.stage.Window;
102
import javafx.stage.WindowEvent;
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
  private MenuBar menuBar;
115
116
  private DefinitionPane definitionPane;
117
  private FileEditorTabPane fileEditorPane;
118
  private HTMLPreviewPane previewPane;
119
120
  private VariableNameInjector variableNameInjector;
121
  private DefinitionSource definitionSource;
122
123
  public MainWindow() {
124
    initLayout();
125
    initOpenDefinitionListener();
126
    initTabAddedListener();
127
    initTabChangedListener();
128
    initPreferences();
129
    initVariableNameInjector();
130
  }
131
132
  /**
133
   * Listen for file editor tab pane to receive an open definition source event.
134
   */
135
  private void initOpenDefinitionListener() {
136
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
137
      (ObservableValue<? extends Path> definitionFile,
138
        final Path oldPath, final Path newPath) -> {
139
        openDefinition( newPath );
140
      } );
141
  }
142
143
  /**
144
   * When tabs are added, hook the various change listeners onto the new tab so
145
   * that the preview pane refreshes as necessary.
146
   */
147
  private void initTabAddedListener() {
148
    final FileEditorTabPane editorPane = getFileEditorPane();
149
150
    // Make sure the text processor kicks off when new files are opened.
151
    final ObservableList<Tab> tabs = editorPane.getTabs();
152
153
    // Update the preview pane on tab changes.
154
    tabs.addListener(
155
      (final Change<? extends Tab> change) -> {
156
        while( change.next() ) {
157
          if( change.wasAdded() ) {
158
            // Multiple tabs can be added simultaneously.
159
            for( final Tab newTab : change.getAddedSubList() ) {
160
              final FileEditorTab tab = (FileEditorTab)newTab;
161
162
              initTextChangeListener( tab );
163
              initCaretParagraphListener( tab );
164
            }
165
          }
166
        }
167
      }
168
    );
169
  }
170
171
  /**
172
   * Reloads the preferences from the previous load.
173
   */
174
  private void initPreferences() {
175
    getFileEditorPane().restorePreferences();
176
    restoreDefinitionSource();
177
  }
178
179
  /**
180
   * Listen for new tab selection events.
181
   */
182
  private void initTabChangedListener() {
183
    final FileEditorTabPane editorPane = getFileEditorPane();
184
185
    // Update the preview pane changing tabs.
186
    editorPane.addTabSelectionListener(
187
      (ObservableValue<? extends Tab> tabPane,
188
        final Tab oldTab, final Tab newTab) -> {
189
190
        // If there was no old tab, then this is a first time load, which
191
        // can be ignored.
192
        if( oldTab != null ) {
193
          if( newTab == null ) {
194
            closeRemainingTab();
195
          } else {
196
            // Synchronize the preview with the edited text.
197
            refreshSelectedTab( (FileEditorTab)newTab );
198
          }
199
        }
200
      }
201
    );
202
  }
203
204
  /**
205
   * Initialize the variable name editor.
206
   */
207
  private void initVariableNameInjector() {
208
    setVariableNameInjector(
209
      new VariableNameInjector( getFileEditorPane(), getDefinitionPane() )
210
    );
211
  }
212
213
  private void initTextChangeListener( final FileEditorTab tab ) {
214
    tab.addTextChangeListener(
215
      (ObservableValue<? extends String> editor,
216
        final String oldValue, final String newValue) -> {
217
        refreshSelectedTab( tab );
218
      }
219
    );
220
  }
221
222
  private void initCaretParagraphListener( final FileEditorTab tab ) {
223
    tab.addCaretParagraphListener(
224
      (ObservableValue<? extends Integer> editor,
225
        final Integer oldValue, final Integer newValue) -> {
226
        refreshSelectedTab( tab );
227
      }
228
    );
229
  }
230
231
  /**
232
   * Called whenever the preview pane becomes out of sync with the file editor
233
   * tab. This can be called when the text changes, the caret paragraph changes,
234
   * or the file tab changes.
235
   *
236
   * @param tab The file editor tab that has been changed in some fashion.
237
   */
238
  private void refreshSelectedTab( final FileEditorTab tab ) {
239
    final HTMLPreviewPane preview = getPreviewPane();
240
    preview.setPath( tab.getPath() );
241
242
    final Processor<String> hpp = new HTMLPreviewProcessor( preview );
243
    final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp );
244
    final Processor<String> mp = new MarkdownProcessor( mcrp );
245
    final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, tab.getCaretPosition() );
246
    final Processor<String> vp = new VariableProcessor( mcip, getResolvedMap() );
247
248
    vp.processChain( tab.getEditorText() );
249
  }
250
251
  /**
252
   * Returns the variable map of interpolated definitions.
253
   *
254
   * @return A map to help dereference variables.
255
   */
256
  private Map<String, String> getResolvedMap() {
257
    return getDefinitionSource().getResolvedMap();
258
  }
259
260
  /**
261
   * Returns the root node for the hierarchical definition source.
262
   *
263
   * @return Data to display in the definition pane.
264
   */
265
  private TreeView<String> getTreeView() {
266
    try {
267
      return getDefinitionSource().asTreeView();
268
    } catch( Exception e ) {
269
      alert( e );
270
    }
271
272
    return new TreeView<>();
273
  }
274
275
  private void openDefinition( final Path path ) {
276
    openDefinition( path.toString() );
277
  }
278
279
  private void openDefinition( final String path ) {
280
    try {
281
      final DefinitionSource ds = createDefinitionSource( path );
282
      setDefinitionSource( ds );
283
      storeDefinitionSource();
284
285
      getDefinitionPane().setRoot( ds.asTreeView() );
286
    } catch( Exception e ) {
287
      alert( e );
288
    }
289
  }
290
291
  private void restoreDefinitionSource() {
292
    final Preferences preferences = getPreferences();
293
    final String source = preferences.get( PREFS_DEFINITION_SOURCE, null );
294
295
    if( source != null ) {
296
      openDefinition( source );
297
    }
298
  }
299
300
  private void storeDefinitionSource() {
301
    final Preferences preferences = getPreferences();
302
    final DefinitionSource ds = getDefinitionSource();
303
304
    preferences.put( PREFS_DEFINITION_SOURCE, ds.toString() );
305
  }
306
307
  /**
308
   * Called when the last open tab is closed. This clears out the preview pane
309
   * and the definition pane.
310
   */
311
  private void closeRemainingTab() {
312
    getPreviewPane().clear();
313
    getDefinitionPane().clear();
314
  }
315
316
  /**
317
   * Called when an exception occurs that warrants the user's attention.
318
   *
319
   * @param e The exception with a message that the user should know about.
320
   */
321
  private void alert( final Exception e ) {
322
    // TODO: Raise a notice.
323
  }
324
325
  //---- File actions -------------------------------------------------------
326
  private void fileNew() {
327
    getFileEditorPane().newEditor();
328
  }
329
330
  private void fileOpen() {
331
    getFileEditorPane().openFileDialog();
332
  }
333
334
  private void fileClose() {
335
    getFileEditorPane().closeEditor( getActiveFileEditor(), true );
336
  }
337
338
  private void fileCloseAll() {
339
    getFileEditorPane().closeAllEditors();
340
  }
341
342
  private void fileSave() {
343
    getFileEditorPane().saveEditor( getActiveFileEditor() );
344
  }
345
346
  private void fileSaveAll() {
347
    getFileEditorPane().saveAllEditors();
348
  }
349
350
  private void fileExit() {
351
    final Window window = getWindow();
352
    Event.fireEvent( window,
353
      new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) );
354
  }
355
356
  //---- Help actions -------------------------------------------------------
357
  private void helpAbout() {
358
    Alert alert = new Alert( AlertType.INFORMATION );
359
    alert.setTitle( get( "Dialog.about.title" ) );
360
    alert.setHeaderText( get( "Dialog.about.header" ) );
361
    alert.setContentText( get( "Dialog.about.content" ) );
362
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
363
    alert.initOwner( getWindow() );
364
365
    alert.showAndWait();
366
  }
367
368
  //---- Convenience accessors ----------------------------------------------
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 Window getWindow() {
378
    return getScene().getWindow();
379
  }
380
381
  private MarkdownEditorPane getActiveEditor() {
382
    return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane());
383
  }
384
385
  private FileEditorTab getActiveFileEditor() {
386
    return getFileEditorPane().getActiveFileEditor();
387
  }
388
389
  //---- Member accessors ---------------------------------------------------
390
  public Scene getScene() {
391
    return this.scene;
392
  }
393
394
  private void setScene( Scene scene ) {
395
    this.scene = scene;
396
  }
397
398
  private FileEditorTabPane getFileEditorPane() {
399
    if( this.fileEditorPane == null ) {
400
      this.fileEditorPane = createFileEditorPane();
401
    }
402
403
    return this.fileEditorPane;
404
  }
405
406
  private synchronized HTMLPreviewPane getPreviewPane() {
407
    if( this.previewPane == null ) {
408
      this.previewPane = createPreviewPane();
409
    }
410
411
    return this.previewPane;
412
  }
413
414
  private void setDefinitionSource( final DefinitionSource definitionSource ) {
415
    this.definitionSource = definitionSource;
416
  }
417
418
  private synchronized DefinitionSource getDefinitionSource() {
419
    if( this.definitionSource == null ) {
420
      this.definitionSource = new EmptyDefinitionSource();
421
    }
422
423
    return this.definitionSource;
424
  }
425
426
  private DefinitionPane getDefinitionPane() {
427
    if( this.definitionPane == null ) {
428
      this.definitionPane = createDefinitionPane();
429
    }
430
431
    return this.definitionPane;
432
  }
433
434
  public VariableNameInjector getVariableNameInjector() {
435
    return this.variableNameInjector;
436
  }
437
438
  public void setVariableNameInjector( final VariableNameInjector injector ) {
439
    this.variableNameInjector = injector;
440
  }
441
442
  private Options getOptions() {
443
    return this.options;
444
  }
445
446
  public MenuBar getMenuBar() {
447
    return this.menuBar;
448
  }
449
450
  public void setMenuBar( MenuBar menuBar ) {
451
    this.menuBar = menuBar;
452
  }
453
454
  //---- Member creators ----------------------------------------------------
455
  private DefinitionSource createDefinitionSource( final String path )
456
    throws MalformedURLException {
457
    return createDefinitionFactory().createDefinitionSource( path );
458
  }
459
460
  /**
461
   * Create an editor pane to hold file editor tabs.
462
   *
463
   * @return A new instance, never null.
464
   */
465
  private FileEditorTabPane createFileEditorPane() {
466
    return new FileEditorTabPane();
467
  }
468
469
  private HTMLPreviewPane createPreviewPane() {
470
    return new HTMLPreviewPane();
471
  }
472
473
  private DefinitionPane createDefinitionPane() {
474
    return new DefinitionPane( getTreeView() );
475
  }
476
477
  private DefinitionFactory createDefinitionFactory() {
478
    return new DefinitionFactory();
479
  }
480
481
  private Node createMenuBar() {
482
    final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull();
483
484
    // File actions
485
    Action fileNewAction = new Action( get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() );
486
    Action fileOpenAction = new Action( get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() );
487
    Action fileCloseAction = new Action( get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull );
488
    Action fileCloseAllAction = new Action( get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull );
489
    Action fileSaveAction = new Action( get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
490
      createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() );
491
    Action fileSaveAllAction = new Action( get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(),
492
      Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
493
    Action fileExitAction = new Action( get( "Main.menu.file.exit" ), null, null, e -> fileExit() );
494
495
    // Edit actions
496
    Action editUndoAction = new Action( get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO,
497
      e -> getActiveEditor().undo(),
498
      createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() );
499
    Action editRedoAction = new Action( get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT,
500
      e -> getActiveEditor().redo(),
501
      createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() );
502
503
    // Insert actions
504
    Action insertBoldAction = new Action( get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD,
505
      e -> getActiveEditor().surroundSelection( "**", "**" ),
506
      activeFileEditorIsNull );
507
    Action insertItalicAction = new Action( get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
508
      e -> getActiveEditor().surroundSelection( "*", "*" ),
509
      activeFileEditorIsNull );
510
    Action insertStrikethroughAction = new Action( get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
511
      e -> getActiveEditor().surroundSelection( "~~", "~~" ),
512
      activeFileEditorIsNull );
513
    Action insertBlockquoteAction = new Action( get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
514
      e -> getActiveEditor().surroundSelection( "\n\n> ", "" ),
515
      activeFileEditorIsNull );
516
    Action insertCodeAction = new Action( get( "Main.menu.insert.code" ), "Shortcut+K", CODE,
517
      e -> getActiveEditor().surroundSelection( "`", "`" ),
518
      activeFileEditorIsNull );
519
    Action insertFencedCodeBlockAction = new Action( get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT,
520
      e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", get( "Main.menu.insert.fenced_code_block.prompt" ) ),
521
      activeFileEditorIsNull );
522
523
    Action insertLinkAction = new Action( get( "Main.menu.insert.link" ), "Shortcut+L", LINK,
524
      e -> getActiveEditor().insertLink(),
525
      activeFileEditorIsNull );
526
    Action insertImageAction = new Action( get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT,
527
      e -> getActiveEditor().insertImage(),
528
      activeFileEditorIsNull );
529
530
    final Action[] headers = new Action[ 6 ];
531
532
    // Insert header actions (H1 ... H6)
533
    for( int i = 1; i <= 6; i++ ) {
534
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
535
      final String markup = String.format( "\n\n%s ", hashes );
536
      final String text = get( "Main.menu.insert.header_" + i );
537
      final String accelerator = "Shortcut+" + i;
538
      final String prompt = get( "Main.menu.insert.header_" + i + ".prompt" );
539
540
      headers[ i - 1 ] = new Action( text, accelerator, HEADER,
541
        e -> getActiveEditor().surroundSelection( markup, "", prompt ),
542
        activeFileEditorIsNull );
543
    }
544
545
    Action insertUnorderedListAction = new Action( get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
546
      e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
547
      activeFileEditorIsNull );
548
    Action insertOrderedListAction = new Action( get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
549
      e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
550
      activeFileEditorIsNull );
551
    Action insertHorizontalRuleAction = new Action( get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
552
      e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
553
      activeFileEditorIsNull );
554
555
    // Help actions
556
    Action helpAboutAction = new Action( get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
557
558
    //---- MenuBar ----
559
    Menu fileMenu = ActionUtils.createMenu( get( "Main.menu.file" ),
560
      fileNewAction,
561
      fileOpenAction,
562
      null,
563
      fileCloseAction,
564
      fileCloseAllAction,
565
      null,
566
      fileSaveAction,
567
      fileSaveAllAction,
568
      null,
569
      fileExitAction );
570
571
    Menu editMenu = ActionUtils.createMenu( get( "Main.menu.edit" ),
572
      editUndoAction,
573
      editRedoAction );
574
575
    Menu insertMenu = ActionUtils.createMenu( get( "Main.menu.insert" ),
576
      insertBoldAction,
577
      insertItalicAction,
578
      insertStrikethroughAction,
579
      insertBlockquoteAction,
580
      insertCodeAction,
581
      insertFencedCodeBlockAction,
582
      null,
583
      insertLinkAction,
584
      insertImageAction,
585
      null,
586
      headers[ 0 ],
587
      headers[ 1 ],
588
      headers[ 2 ],
589
      headers[ 3 ],
590
      headers[ 4 ],
591
      headers[ 5 ],
592
      null,
593
      insertUnorderedListAction,
594
      insertOrderedListAction,
595
      insertHorizontalRuleAction );
596
597
    Menu helpMenu = ActionUtils.createMenu( get( "Main.menu.help" ),
598
      helpAboutAction );
599
600
    menuBar = new MenuBar( fileMenu, editMenu, insertMenu, helpMenu );
601
602
    //---- ToolBar ----
603
    ToolBar toolBar = ActionUtils.createToolBar(
604
      fileNewAction,
605
      fileOpenAction,
606
      fileSaveAction,
607
      null,
608
      editUndoAction,
609
      editRedoAction,
610
      null,
611
      insertBoldAction,
612
      insertItalicAction,
613
      insertBlockquoteAction,
614
      insertCodeAction,
615
      insertFencedCodeBlockAction,
616
      null,
617
      insertLinkAction,
618
      insertImageAction,
619
      null,
620
      headers[ 0 ],
621
      null,
622
      insertUnorderedListAction,
623
      insertOrderedListAction );
624
625
    return new VBox( menuBar, toolBar );
626
  }
627
628
  /**
629
   * Creates a boolean property that is bound to another boolean value of the
630
   * active editor.
631
   */
632
  private BooleanProperty createActiveBooleanProperty(
633
    final Function<FileEditorTab, ObservableBooleanValue> func ) {
634
635
    final BooleanProperty b = new SimpleBooleanProperty();
636
    final FileEditorTab tab = getActiveFileEditor();
637
638
    if( tab != null ) {
639
      b.bind( func.apply( tab ) );
640
    }
641
642
    getFileEditorPane().activeFileEditorProperty().addListener(
643
      (observable, oldFileEditor, newFileEditor) -> {
644
        b.unbind();
645
646
        if( newFileEditor != null ) {
647
          b.bind( func.apply( newFileEditor ) );
648
        } else {
649
          b.set( false );
650
        }
651
      }
652
    );
653
654
    return b;
655
  }
656
657
  private void initLayout() {
658
    final SplitPane splitPane = new SplitPane(
659
      getDefinitionPane().getNode(),
660
      getFileEditorPane().getNode(),
661
      getPreviewPane().getNode() );
662
663
    splitPane.setDividerPositions(
664
      getFloat( K_PANE_SPLIT_DEFINITION, .10f ),
665
      getFloat( K_PANE_SPLIT_EDITOR, .45f ),
666
      getFloat( K_PANE_SPLIT_PREVIEW, .45f ) );
667
668
    // See: http://broadlyapplicable.blogspot.ca/2015/03/javafx-capture-restorePreferences-splitpane.html
669
    final BorderPane borderPane = new BorderPane();
670
    borderPane.setPrefSize( 1024, 800 );
671
    borderPane.setTop( createMenuBar() );
672
    borderPane.setCenter( splitPane );
673
674
    final Scene appScene = new Scene( borderPane );
675
    setScene( appScene );
676
    appScene.getStylesheets().add( STYLESHEET_SCENE );
677
    appScene.windowProperty().addListener(
678
      (observable, oldWindow, newWindow) -> {
679
        newWindow.setOnCloseRequest( e -> {
680
          if( !getFileEditorPane().closeAllEditors() ) {
681
            e.consume();
682
          }
683
        } );
684
685
        // Workaround JavaFX bug: deselect menubar if window loses focus.
686
        newWindow.focusedProperty().addListener(
687
          (obs, oldFocused, newFocused) -> {
688
            if( !newFocused ) {
689
              // Send an ESC key event to the menubar
690
              this.menuBar.fireEvent(
691
                new KeyEvent(
692
                  KEY_PRESSED, CHAR_UNDEFINED, "", ESCAPE,
693
                  false, false, false, false ) );
694
            }
695
          }
696
        );
697
      }
698
    );
699
  }
700
}
1701
A src/main/java/com/scrivenvar/Messages.java
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 java.text.MessageFormat;
30
import java.util.ResourceBundle;
31
import java.util.Stack;
32
import static com.scrivenvar.Constants.APP_BUNDLE_NAME;
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(APP_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
}
1138
A src/main/java/com/scrivenvar/Services.java
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
}
166
A src/main/java/com/scrivenvar/controls/BrowseDirectoryButton.java
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
}
161
A src/main/java/com/scrivenvar/controls/BrowseFileButton.java
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
}
1115
A src/main/java/com/scrivenvar/controls/EscapeTextField.java
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
}
178
A src/main/java/com/scrivenvar/controls/FlagCheckBox.java
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
}
184
A src/main/java/com/scrivenvar/controls/WebHyperlink.java
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
}
158
A src/main/java/com/scrivenvar/decorators/RVariableDecorator.java
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
}
150
A src/main/java/com/scrivenvar/decorators/VariableDecorator.java
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
}
147
A src/main/java/com/scrivenvar/decorators/YamlVariableDecorator.java
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
}
156
A src/main/java/com/scrivenvar/definition/AbstractDefinitionSource.java
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
/**
31
 * Implements common behaviour for definition sources.
32
 * 
33
 * @author White Magic Software, Ltd.
34
 */
35
public abstract class AbstractDefinitionSource implements DefinitionSource {
36
}
137
A src/main/java/com/scrivenvar/definition/DefinitionFactory.java
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 com.scrivenvar.Services;
31
import com.scrivenvar.definition.yaml.YamlFileDefinitionSource;
32
import com.scrivenvar.predicates.files.FileTypePredicate;
33
import com.scrivenvar.service.Settings;
34
import java.io.File;
35
import java.net.MalformedURLException;
36
import java.net.URI;
37
import java.net.URISyntaxException;
38
import java.net.URL;
39
import java.nio.file.Path;
40
import java.nio.file.Paths;
41
import java.util.Iterator;
42
import java.util.List;
43
44
/**
45
 * Responsible for creating objects that can read and write definition data
46
 * sources. The data source could be YAML, TOML, JSON, flat files, or from a
47
 * database.
48
 *
49
 * @author White Magic Software, Ltd.
50
 */
51
public class DefinitionFactory {
52
53
  /**
54
   * Refers to filename extension settings in the configuration file. Do not
55
   * terminate this key prefix with a period.
56
   */
57
  private static final String EXTENSIONS_PREFIX = "file.ext.definition";
58
59
  private final Settings settings = Services.load( Settings.class );
60
61
  /**
62
   * Default (empty) constructor.
63
   */
64
  public DefinitionFactory() {
65
  }
66
67
  /**
68
   * Creates a definition source that can read and write files that match the
69
   * given file type (from the path).
70
   *
71
   * @param path Reference to a variable definition file.
72
   *
73
   * @return
74
   */
75
  public DefinitionSource fileDefinitionSource( final Path path ) {
76
    final Settings properties = getSettings();
77
    final Iterator<String> keys = properties.getKeys( EXTENSIONS_PREFIX );
78
79
    DefinitionSource result = new EmptyDefinitionSource();
80
81
    while( keys.hasNext() ) {
82
      final String key = keys.next();
83
      final List<String> patterns = properties.getStringSettingList( key );
84
      final FileTypePredicate predicate = new FileTypePredicate( patterns );
85
86
      if( predicate.test( path.toFile() ) ) {
87
        final String filetype = key.replace( EXTENSIONS_PREFIX + ".", "" );
88
89
        result = createFileDefinitionSource( filetype, path );
90
      }
91
    }
92
93
    return result;
94
  }
95
96
  public DefinitionSource createDefinitionSource( final String path ) {
97
98
    final String protocol = getProtocol( path );
99
    DefinitionSource result = new EmptyDefinitionSource();
100
101
    switch( protocol ) {
102
      case "file":
103
        result = fileDefinitionSource( Paths.get( path ) );
104
        break;
105
106
      default:
107
        unknownDefinitionSource( protocol, path );
108
        break;
109
    }
110
111
    return result;
112
  }
113
114
  /**
115
   * Creates a definition source based on the file type.
116
   *
117
   * @param filetype Property key name suffix from settings.properties file.
118
   * @param path Path to the file that corresponds to the extension.
119
   *
120
   * @return A DefinitionSource capable of parsing the data stored at the path.
121
   */
122
  private DefinitionSource createFileDefinitionSource(
123
    final String filetype, final Path path ) {
124
    DefinitionSource result = new EmptyDefinitionSource();
125
126
    switch( filetype ) {
127
      case "yaml":
128
        result = new YamlFileDefinitionSource( path );
129
        break;
130
131
      default:
132
        unknownDefinitionSource( filetype, path.toString() );
133
        break;
134
    }
135
136
    return result;
137
  }
138
139
  /**
140
   * Throws IllegalArgumentException because the given path could not be
141
   * recognized.
142
   *
143
   * @param type The detected path type (protocol, file extension, etc.).
144
   * @param path The path to a source of definitions.
145
   */
146
  private void unknownDefinitionSource( final String type, final String path ) {
147
    throw new IllegalArgumentException(
148
      "Unknown type '" + type + "' for " + path + "."
149
    );
150
  }
151
152
  private Settings getSettings() {
153
    return this.settings;
154
  }
155
156
  /**
157
   * Returns the protocol for a given URI or filename.
158
   *
159
   * @param source Determine the protocol for this URI or filename.
160
   *
161
   * @return The protocol for the given source.
162
   */
163
  private String getProtocol( final String source ) {
164
    String protocol = null;
165
166
    try {
167
      final URI uri = new URI( source );
168
169
      if( uri.isAbsolute() ) {
170
        protocol = uri.getScheme();
171
      } else {
172
        final URL url = new URL( source );
173
        protocol = url.getProtocol();
174
      }
175
    } catch( final URISyntaxException | MalformedURLException e ) {
176
      // Could be HTTP, HTTPS?
177
      if( source.startsWith( "//" ) ) {
178
        throw new IllegalArgumentException( "Relative context: " + source );
179
      } else {
180
        final File file = new File( source );
181
        protocol = getProtocol( file );
182
      }
183
    }
184
185
    return protocol;
186
  }
187
188
  /**
189
   * Returns the protocol for a given file.
190
   *
191
   * @param file Determine the protocol for this file.
192
   *
193
   * @return The protocol for the given file.
194
   */
195
  private String getProtocol( final File file ) {
196
    String result;
197
198
    try {
199
      result = file.toURI().toURL().getProtocol();
200
    } catch( Exception e ) {
201
      result = "unknown";
202
    }
203
204
    return result;
205
  }
206
}
1207
A src/main/java/com/scrivenvar/definition/DefinitionPane.java
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 com.scrivenvar.AbstractPane;
31
import static com.scrivenvar.definition.yaml.YamlParser.SEPARATOR;
32
import com.scrivenvar.predicates.strings.ContainsPredicate;
33
import com.scrivenvar.predicates.strings.StartsPredicate;
34
import com.scrivenvar.predicates.strings.StringPredicate;
35
import static com.scrivenvar.util.Lists.getFirst;
36
import java.util.List;
37
import javafx.collections.ObservableList;
38
import javafx.scene.Node;
39
import javafx.scene.control.MultipleSelectionModel;
40
import javafx.scene.control.SelectionMode;
41
import javafx.scene.control.TreeItem;
42
import javafx.scene.control.TreeView;
43
44
/**
45
 * Provides a list of variables that can be referenced in the editor.
46
 *
47
 * @author White Magic Software, Ltd.
48
 */
49
public class DefinitionPane extends AbstractPane {
50
51
  /**
52
   * Trimmed off the end of a word to match a variable name.
53
   */
54
  private final static String TERMINALS = ":;,.!?-/\\¡¿";
55
56
  private TreeView<String> treeView;
57
58
  /**
59
   * Constructs a definition pane with a given tree view root.
60
   *
61
   * @see YamlTreeAdapter.adapt
62
   * @param root The root of the variable definition tree.
63
   */
64
  public DefinitionPane( final TreeView<String> root ) {
65
    setTreeView( root );
66
    initTreeView();
67
  }
68
69
  /**
70
   * Changes the root node of the tree view. Swaps the current root node for the
71
   * root node of the given
72
   *
73
   * @param treeView The treeview containing a new root node; if the parameter
74
   * is null, the tree is cleared.
75
   */
76
  public void setRoot( final TreeView<String> treeView ) {
77
    getTreeView().setRoot( treeView == null ? null : treeView.getRoot() );
78
  }
79
80
  /**
81
   * Clears the treeview by setting the root node to null.
82
   */
83
  public void clear() {
84
    setRoot( null );
85
  }
86
87
  /**
88
   * Finds a tree item with a value that exactly matches the given word.
89
   *
90
   * @param trunk The root item containing a list of nodes to search.
91
   * @param word The value of the item to find.
92
   * @param predicate Helps determine whether the node value matches the word.
93
   *
94
   * @return The item that matches the given word, or null if not found.
95
   */
96
  private TreeItem<String> findNode(
97
    final TreeItem<String> trunk,
98
    final StringPredicate predicate ) {
99
    final List<TreeItem<String>> branches = trunk.getChildren();
100
    TreeItem<String> result = null;
101
102
    for( final TreeItem<String> leaf : branches ) {
103
      if( predicate.test( leaf.getValue() ) ) {
104
        result = leaf;
105
        break;
106
      }
107
    }
108
109
    return result;
110
  }
111
112
  /**
113
   * Calls findNode with the EqualsPredicate.
114
   *
115
   * @see findNode( TreeItem, String, Predicate )
116
   * @return The result from findNode.
117
   */
118
  private TreeItem<String> findStartsNode(
119
    final TreeItem<String> trunk,
120
    final String word ) {
121
    return findNode( trunk, new StartsPredicate( word ) );
122
  }
123
124
  /**
125
   * Calls findNode with the ContainsPredicate.
126
   *
127
   * @see findNode( TreeItem, String, Predicate )
128
   * @return The result from findNode.
129
   */
130
  private TreeItem<String> findSubstringNode(
131
    final TreeItem<String> trunk,
132
    final String word ) {
133
    return findNode( trunk, new ContainsPredicate( word ) );
134
  }
135
136
  /**
137
   * Finds a node that matches a prefix and suffix specified by the given path
138
   * variable. The prefix must match a valid node value. The suffix refers to
139
   * the start of a string that matches zero or more children of the node
140
   * specified by the prefix. The algorithm has the following cases:
141
   *
142
   * <ol>
143
   * <li>Path is empty, return first child.</li>
144
   * <li>Path contains a complete match, return corresponding node.</li>
145
   * <li>Path contains a partial match, return nearest node.</li>
146
   * <li>Path contains a complete and partial match, return nearest node.</li>
147
   * </ol>
148
   *
149
   * @param word The word typed by the user, which contains dot-separated node
150
   * names that represent a path within the YAML tree plus a partial variable
151
   * name match (for a node).
152
   *
153
   * @return The node value that starts with the suffix portion of the given
154
   * path, never null.
155
   */
156
  public TreeItem<String> findNode( final String word ) {
157
    String path = word;
158
159
    TreeItem<String> cItem = getTreeRoot();
160
    TreeItem<String> pItem = cItem;
161
162
    int index = path.indexOf( SEPARATOR );
163
164
    while( index >= 0 ) {
165
      final String node = path.substring( 0, index );
166
      path = path.substring( index + 1 );
167
168
      if( (cItem = findStartsNode( cItem, node )) == null ) {
169
        break;
170
      }
171
172
      index = path.indexOf( SEPARATOR );
173
      pItem = cItem;
174
    }
175
176
    // Find the node that starts with whatever the user typed.
177
    cItem = findStartsNode( pItem, path );
178
179
    // If there was no matching node, then find a substring match.
180
    if( cItem == null ) {
181
      cItem = findSubstringNode( pItem, path );
182
    }
183
184
    // If neither starts with nor substring matched a node, revert to the last
185
    // known valid node.
186
    if( cItem == null ) {
187
      cItem = pItem;
188
    }
189
190
    return sanitize( cItem );
191
  }
192
193
  /**
194
   * Returns the leaf that matches the given value. If the value is terminally
195
   * punctuated, the punctuation is removed if no match was found.
196
   *
197
   * @param value The value to find, never null.
198
   *
199
   * @return The leaf that contains the given value, or null if neither the
200
   * original value nor the terminally-trimmed value was found.
201
   */
202
  public VariableTreeItem<String> findLeaf( final String value ) {
203
    final VariableTreeItem<String> root = getTreeRoot();
204
    final VariableTreeItem<String> leaf = root.findLeaf( value );
205
206
    return leaf == null
207
      ? root.findLeaf( rtrimTerminalPunctuation( value ) )
208
      : leaf;
209
  }
210
211
  /**
212
   * Removes punctuation from the end of a string. The character set includes:
213
   * <code>:;,.!?-/\¡¿</code>.
214
   *
215
   * @param s The string to trim, never null.
216
   *
217
   * @return The string trimmed of all terminal characters from the end
218
   */
219
  private String rtrimTerminalPunctuation( final String s ) {
220
    final StringBuilder result = new StringBuilder( s.trim() );
221
222
    while( TERMINALS.contains( "" + result.charAt( result.length() - 1 ) ) ) {
223
      result.setLength( result.length() - 1 );
224
    }
225
226
    return result.toString();
227
  }
228
229
  /**
230
   * Returns the tree root if either item or its first child are null.
231
   *
232
   * @param item The item to make null safe.
233
   *
234
   * @return A non-null TreeItem, possibly the root item (to avoid null).
235
   */
236
  private TreeItem<String> sanitize( final TreeItem<String> item ) {
237
    final TreeItem<String> result = item == getTreeRoot()
238
      ? getFirst( item.getChildren() )
239
      : item;
240
241
    return result == null ? item : result;
242
  }
243
244
  /**
245
   * Expands the node to the root, recursively.
246
   *
247
   * @param <T> The type of tree item to expand (usually String).
248
   * @param node The node to expand.
249
   */
250
  public <T> void expand( final TreeItem<T> node ) {
251
    if( node != null ) {
252
      expand( node.getParent() );
253
254
      if( !node.isLeaf() ) {
255
        node.setExpanded( true );
256
      }
257
    }
258
  }
259
260
  public void select( final TreeItem<String> item ) {
261
    clearSelection();
262
    selectItem( getTreeView().getRow( item ) );
263
  }
264
265
  private void clearSelection() {
266
    getSelectionModel().clearSelection();
267
  }
268
269
  private void selectItem( final int row ) {
270
    getSelectionModel().select( row );
271
  }
272
273
  /**
274
   * Collapses the tree, recursively.
275
   */
276
  public void collapse() {
277
    collapse( getTreeRoot().getChildren() );
278
  }
279
280
  /**
281
   * Collapses the tree, recursively.
282
   *
283
   * @param <T> The type of tree item to expand (usually String).
284
   * @param node The nodes to collapse.
285
   */
286
  private <T> void collapse( ObservableList<TreeItem<T>> nodes ) {
287
    for( final TreeItem<T> node : nodes ) {
288
      node.setExpanded( false );
289
      collapse( node.getChildren() );
290
    }
291
  }
292
293
  private void initTreeView() {
294
    getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
295
  }
296
297
  /**
298
   * Returns the root node to the tree view.
299
   *
300
   * @return getTreeView()
301
   */
302
  public Node getNode() {
303
    return getTreeView();
304
  }
305
306
  private MultipleSelectionModel getSelectionModel() {
307
    return getTreeView().getSelectionModel();
308
  }
309
310
  /**
311
   * Returns the tree view that contains the YAML definition hierarchy.
312
   *
313
   * @return A non-null instance.
314
   */
315
  private TreeView<String> getTreeView() {
316
    return this.treeView;
317
  }
318
319
  /**
320
   * Returns the root of the tree.
321
   *
322
   * @return The first node added to the YAML definition tree.
323
   */
324
  private VariableTreeItem<String> getTreeRoot() {
325
    return (VariableTreeItem<String>)getTreeView().getRoot();
326
  }
327
328
  public <T> boolean isRoot( final TreeItem<T> item ) {
329
    return getTreeRoot().equals( item );
330
  }
331
332
  /**
333
   * Sets the tree view (called by the constructor).
334
   *
335
   * @param treeView
336
   */
337
  private void setTreeView( final TreeView<String> treeView ) {
338
    if( treeView != null ) {
339
      this.treeView = treeView;
340
    }
341
  }
342
}
1343
A src/main/java/com/scrivenvar/definition/DefinitionSource.java
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.io.IOException;
31
import java.util.Map;
32
import javafx.scene.control.TreeView;
33
34
/**
35
 * Represents behaviours for reading and writing variable definitions.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public interface DefinitionSource {
40
41
  /**
42
   * Creates a TreeView from this definition source. The definition source is
43
   * responsible for observing the TreeView instance for changes and persisting
44
   * them, if needed.
45
   *
46
   * @return A hierarchical tree suitable for displaying in the definition pane.
47
   *
48
   * @throws IOException Could not obtain the definition source data.
49
   */
50
  public TreeView<String> asTreeView() throws IOException;
51
52
  /**
53
   * Returns all the strings with their values resolved in a flat hierarchy.
54
   * This copies all the keys and resolved values into a new map.
55
   *
56
   * @return The new map created with all values having been resolved,
57
   * recursively.
58
   */
59
  public Map<String, String> getResolvedMap();
60
61
  /**
62
   * Must return a re-loadable path to the data source. For a file, this is the
63
   * absolute file path. For a database, this could be the JDBC connection. For
64
   * a web site, this might be the GET url.
65
   *
66
   * @return A non-null, non-empty string.
67
   */
68
  @Override
69
  public String toString();
70
}
171
A src/main/java/com/scrivenvar/definition/EmptyDefinitionSource.java
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.io.IOException;
31
import java.util.HashMap;
32
import java.util.Map;
33
import javafx.scene.control.TreeView;
34
35
/**
36
 * Creates a definition source that has no information to load or save.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public class EmptyDefinitionSource extends AbstractDefinitionSource {
41
42
  public EmptyDefinitionSource() {
43
  }
44
45
  @Override
46
  public TreeView<String> asTreeView() throws IOException {
47
    return new TreeView<>();
48
  }
49
50
  @Override
51
  public Map<String, String> getResolvedMap() {
52
    return new HashMap<>();
53
  }
54
55
}
156
A src/main/java/com/scrivenvar/definition/VariableTreeItem.java
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 com.scrivenvar.decorators.VariableDecorator;
31
import com.scrivenvar.decorators.YamlVariableDecorator;
32
import static com.scrivenvar.definition.yaml.YamlParser.SEPARATOR;
33
import static com.scrivenvar.editors.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
}
1192
A src/main/java/com/scrivenvar/definition/yaml/FileDefinitionSource.java
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.yaml;
29
30
import com.scrivenvar.definition.AbstractDefinitionSource;
31
import java.nio.file.Path;
32
33
/**
34
 * Implements common behaviour for file definition sources.
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public abstract class FileDefinitionSource extends AbstractDefinitionSource {
39
40
  private Path path;
41
42
  /**
43
   * Constructs a new file definition source that can read and write data in the
44
   * hierarchical format contained within the file location specified by the
45
   * path.
46
   *
47
   * @param path Must not be null.
48
   */
49
  public FileDefinitionSource( final Path path ) {
50
    setPath( path );
51
  }
52
53
  private void setPath( final Path path ) {
54
    this.path = path;
55
  }
56
57
  protected Path getPath() {
58
    return this.path;
59
  }
60
61
  /**
62
   * Returns the path represented by this object.
63
   *
64
   * @return The
65
   */
66
  @Override
67
  public String toString() {
68
    return getPath().toString();
69
  }
70
}
171
A src/main/java/com/scrivenvar/definition/yaml/YamlFileDefinitionSource.java
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.yaml;
29
30
import static com.scrivenvar.Messages.get;
31
import java.io.IOException;
32
import java.io.InputStream;
33
import java.nio.file.Files;
34
import java.nio.file.Path;
35
import java.util.Map;
36
import javafx.scene.control.TreeView;
37
38
/**
39
 * Represents a definition data source for YAML files.
40
 *
41
 * @author White Magic Software, Ltd.
42
 */
43
public class YamlFileDefinitionSource extends FileDefinitionSource {
44
45
  private YamlTreeAdapter yamlTreeAdapter;
46
  private YamlParser yamlParser;
47
48
  /**
49
   * Constructs a new YAML definition source, populated from the given file.
50
   *
51
   * @param path Path to the YAML definition file.
52
   */
53
  public YamlFileDefinitionSource( final Path path ) {
54
    super( path );
55
  }
56
57
  /**
58
   * TODO: Associate variable file with path to current file.
59
   *
60
   * @return The TreeView for this definition source.
61
   *
62
   * @throws IOException
63
   */
64
  @Override
65
  public TreeView<String> asTreeView() throws IOException {
66
67
    try( final InputStream in = Files.newInputStream( getPath() ) ) {
68
      return getYamlTreeAdapter().adapt(
69
        in,
70
        get( "Pane.defintion.node.root.title" )
71
      );
72
    }
73
  }
74
75
  @Override
76
  public Map<String, String> getResolvedMap() {
77
    return getYamlParser().createResolvedMap();
78
  }
79
80
  private YamlTreeAdapter getYamlTreeAdapter() {
81
    if( this.yamlTreeAdapter == null ) {
82
      setYamlTreeAdapter( new YamlTreeAdapter( getYamlParser() ) );
83
    }
84
85
    return this.yamlTreeAdapter;
86
  }
87
88
  private void setYamlTreeAdapter( final YamlTreeAdapter yamlTreeAdapter ) {
89
    this.yamlTreeAdapter = yamlTreeAdapter;
90
  }
91
92
  private YamlParser getYamlParser() {
93
    if( this.yamlParser == null ) {
94
      setYamlParser( new YamlParser() );
95
    }
96
97
    return this.yamlParser;
98
  }
99
100
  private void setYamlParser( final YamlParser yamlParser ) {
101
    this.yamlParser = yamlParser;
102
  }
103
104
  private InputStream asStream( final String resource ) {
105
    return getClass().getResourceAsStream( resource );
106
  }
107
}
1108
A src/main/java/com/scrivenvar/definition/yaml/YamlParser.java
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.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 com.scrivenvar.decorators.VariableDecorator;
39
import com.scrivenvar.decorators.YamlVariableDecorator;
40
import java.io.IOException;
41
import java.io.InputStream;
42
import java.io.Writer;
43
import java.security.InvalidParameterException;
44
import java.text.MessageFormat;
45
import java.util.HashMap;
46
import java.util.Map;
47
import java.util.Map.Entry;
48
import java.util.regex.Matcher;
49
import java.util.regex.Pattern;
50
import org.yaml.snakeyaml.DumperOptions;
51
52
/**
53
 * <p>
54
 * This program loads a YAML document into memory, scans for variable
55
 * declarations, then substitutes any self-referential values back into the
56
 * document. Its output is the given YAML document without any variables.
57
 * Variables in the YAML document are denoted using a bracketed dollar symbol
58
 * syntax. For example: $field.name$. Some nomenclature to keep from going
59
 * squirrely, consider:
60
 * </p>
61
 *
62
 * <pre>
63
 *   root:
64
 *     node:
65
 *       name: $field.name$
66
 *   field:
67
 *     name: Alan Turing
68
 * </pre>
69
 *
70
 * The various components of the given YAML are called:
71
 *
72
 * <ul>
73
 * <li><code>$field.name$</code> - delimited reference</li>
74
 * <li><code>field.name</code> - reference</li>
75
 * <li><code>name</code> - YAML field</li>
76
 * <li><code>Alan Turing</code> - (dereferenced) field value</li>
77
 * </ul>
78
 *
79
 * @author White Magic Software, Ltd.
80
 */
81
public class YamlParser {
82
83
  /**
84
   * Separates YAML variable nodes (e.g., the dots in
85
   * <code>$root.node.var$</code>).
86
   */
87
  public static final String SEPARATOR = ".";
88
89
  private final static int GROUP_DELIMITED = 1;
90
  private final static int GROUP_REFERENCE = 2;
91
92
  private final static VariableDecorator VARIABLE_DECORATOR
93
    = new YamlVariableDecorator();
94
95
  /**
96
   * Compiled version of DEFAULT_REGEX.
97
   */
98
  private final static Pattern REGEX_PATTERN
99
    = Pattern.compile( YamlVariableDecorator.REGEX );
100
101
  /**
102
   * Should be JsonPointer.SEPARATOR, but Jackson YAML uses magic values.
103
   */
104
  private final static char SEPARATOR_YAML = '/';
105
106
  /**
107
   * Start of the Universe (the YAML document node that contains all others).
108
   */
109
  private ObjectNode documentRoot;
110
111
  /**
112
   * Map of references to dereferenced field values.
113
   */
114
  private Map<String, String> references;
115
116
  public YamlParser() {
117
  }
118
119
  /**
120
   * Returns the given string with all the delimited references swapped with
121
   * their recursively resolved values.
122
   *
123
   * @param text The text to parse with zero or more delimited references to
124
   * replace.
125
   *
126
   * @return The substituted value.
127
   */
128
  public String substitute( String text ) {
129
    final Matcher matcher = patternMatch( text );
130
    final Map<String, String> map = getReferences();
131
132
    while( matcher.find() ) {
133
      final String key = matcher.group( GROUP_DELIMITED );
134
      final String value = map.get( key );
135
136
      if( value == null ) {
137
        missing( text );
138
      } else {
139
        text = text.replace( key, value );
140
      }
141
    }
142
143
    return text;
144
  }
145
146
  /**
147
   * Returns all the strings with their values resolved in a flat hierarchy.
148
   * This copies all the keys and resolved values into a new map.
149
   *
150
   * @return The new map created with all values having been resolved,
151
   * recursively.
152
   */
153
  public Map<String, String> createResolvedMap() {
154
    final Map<String, String> map = new HashMap<>( 1024 );
155
156
    resolve( getDocumentRoot(), "", map );
157
158
    return map;
159
  }
160
161
  /**
162
   * Iterate over a given root node (at any level of the tree) and adapt each
163
   * leaf node.
164
   *
165
   * @param rootNode A JSON node (YAML node) to adapt.
166
   */
167
  private void resolve(
168
    final JsonNode rootNode, final String path, final Map<String, String> map ) {
169
170
    if( rootNode != null ) {
171
      rootNode.fields().forEachRemaining(
172
        (Entry<String, JsonNode> leaf) -> resolve( leaf, path, map )
173
      );
174
    }
175
  }
176
177
  /**
178
   * Recursively adapt each rootNode to a corresponding rootItem.
179
   *
180
   * @param rootNode The node to adapt.
181
   */
182
  private void resolve(
183
    final Entry<String, JsonNode> rootNode,
184
    final String path,
185
    final Map<String, String> map ) {
186
187
    final JsonNode leafNode = rootNode.getValue();
188
    final String key = rootNode.getKey();
189
190
    if( leafNode.isValueNode() ) {
191
      final String value = rootNode.getValue().asText();
192
193
      map.put( VARIABLE_DECORATOR.decorate( path + key ), substitute( value ) );
194
    }
195
196
    if( leafNode.isObject() ) {
197
      resolve( leafNode, path + key + SEPARATOR, map );
198
    }
199
  }
200
201
  /**
202
   * Reads the first document from the given stream of YAML data and returns a
203
   * corresponding object that represents the YAML hierarchy. The calling class
204
   * is responsible for closing the stream. Calling classes should use
205
   * <code>JsonNode.fields()</code> to walk through the YAML tree of fields.
206
   *
207
   * @param in The input stream containing YAML content.
208
   *
209
   * @return An object hierarchy to represent the content.
210
   *
211
   * @throws IOException Could not read the stream.
212
   */
213
  public JsonNode process( final InputStream in ) throws IOException {
214
215
    final ObjectNode root = (ObjectNode)getObjectMapper().readTree( in );
216
    setDocumentRoot( root );
217
    process( root );
218
    return getDocumentRoot();
219
  }
220
221
  /**
222
   * Iterate over a given root node (at any level of the tree) and process each
223
   * leaf node.
224
   *
225
   * @param root A node to process.
226
   */
227
  private void process( final JsonNode root ) {
228
    root.fields().forEachRemaining( this::process );
229
  }
230
231
  /**
232
   * Process the given field, which is a named node. This is where the
233
   * application does the up-front work of mapping references to their fully
234
   * recursively dereferenced values.
235
   *
236
   * @param field The named node.
237
   */
238
  private void process( final Entry<String, JsonNode> field ) {
239
    final JsonNode node = field.getValue();
240
241
    if( node.isObject() ) {
242
      process( node );
243
    } else {
244
      final JsonNode fieldValue = field.getValue();
245
246
      // Only basic data types can be parsed into variable values. For
247
      // node structures, YAML has a built-in mechanism.
248
      if( fieldValue.isValueNode() ) {
249
        try {
250
          resolve( fieldValue.asText() );
251
        } catch( StackOverflowError e ) {
252
          throw new IllegalArgumentException(
253
            "Unresolvable: " + node.textValue() + " = " + fieldValue );
254
        }
255
      }
256
    }
257
  }
258
259
  /**
260
   * Inserts the delimited references and field values into the cache. This will
261
   * overwrite existing references.
262
   *
263
   * @param fieldValue YAML field containing zero or more delimited references.
264
   * If it contains a delimited reference, the parameter is modified with the
265
   * dereferenced value before it is returned.
266
   *
267
   * @return fieldValue without delimited references.
268
   */
269
  private String resolve( String fieldValue ) {
270
    final Matcher matcher = patternMatch( fieldValue );
271
272
    while( matcher.find() ) {
273
      final String delimited = matcher.group( GROUP_DELIMITED );
274
      final String reference = matcher.group( GROUP_REFERENCE );
275
      final String dereference = resolve( lookup( reference ) );
276
277
      fieldValue = fieldValue.replace( delimited, dereference );
278
279
      // This will perform some superfluous calls by overwriting existing
280
      // items in the delimited reference map.
281
      put( delimited, dereference );
282
    }
283
284
    return fieldValue;
285
  }
286
287
  /**
288
   * Inserts a key/value pair into the references map. The map retains
289
   * references and dereferenced values found in the YAML. If the reference
290
   * already exists, this will overwrite with a new value.
291
   *
292
   * @param delimited The variable name.
293
   * @param dereferenced The resolved value.
294
   */
295
  private void put( String delimited, String dereferenced ) {
296
    if( dereferenced.isEmpty() ) {
297
      missing( delimited );
298
    } else {
299
      getReferences().put( delimited, dereferenced );
300
    }
301
  }
302
303
  /**
304
   * Writes the modified YAML document to standard output.
305
   */
306
  private void writeDocument() throws IOException {
307
    getObjectMapper().writeValue( System.out, getDocumentRoot() );
308
  }
309
310
  /**
311
   * Called when a delimited reference is dereferenced to an empty string. This
312
   * should produce a warning for the user.
313
   *
314
   * @param delimited Delimited reference with no derived value.
315
   */
316
  private void missing( final String delimited ) {
317
    throw new InvalidParameterException(
318
      MessageFormat.format( "Missing value for '{0}'.", delimited ) );
319
  }
320
321
  /**
322
   * Returns a REGEX_PATTERN matcher for the given text.
323
   *
324
   * @param text The text that contains zero or more instances of a
325
   * REGEX_PATTERN that can be found using the regular expression.
326
   */
327
  private Matcher patternMatch( String text ) {
328
    return getPattern().matcher( text );
329
  }
330
331
  /**
332
   * Finds the YAML value for a reference.
333
   *
334
   * @param reference References a value in the YAML document.
335
   *
336
   * @return The dereferenced value.
337
   */
338
  private String lookup( final String reference ) {
339
    return getDocumentRoot().at( asPath( reference ) ).asText();
340
  }
341
342
  /**
343
   * Converts a reference (not delimited) to a path that can be used to find a
344
   * value that should exist inside the YAML document.
345
   *
346
   * @param reference The reference to convert to a YAML document path.
347
   *
348
   * @return The reference with a leading slash and its separator characters
349
   * converted to slashes.
350
   */
351
  private String asPath( final String reference ) {
352
    return SEPARATOR_YAML + reference.replace( getDelimitedSeparator(), SEPARATOR_YAML );
353
  }
354
355
  /**
356
   * Sets the parent node for the entire YAML document tree.
357
   *
358
   * @param documentRoot The parent node.
359
   */
360
  private void setDocumentRoot( ObjectNode documentRoot ) {
361
    this.documentRoot = documentRoot;
362
  }
363
364
  /**
365
   * Returns the parent node for the entire YAML document tree.
366
   *
367
   * @return The parent node.
368
   */
369
  private ObjectNode getDocumentRoot() {
370
    return this.documentRoot;
371
  }
372
373
  /**
374
   * Returns the compiled regular expression REGEX_PATTERN used to match
375
   * delimited references.
376
   *
377
   * @return A compiled regex for use with the Matcher.
378
   */
379
  private Pattern getPattern() {
380
    return REGEX_PATTERN;
381
  }
382
383
  /**
384
   * Returns the list of references mapped to dereferenced values.
385
   *
386
   * @return
387
   */
388
  private Map<String, String> getReferences() {
389
    if( this.references == null ) {
390
      this.references = createReferences();
391
    }
392
393
    return this.references;
394
  }
395
396
  /**
397
   * Subclasses can override this method to insert their own map.
398
   *
399
   * @return An empty HashMap, never null.
400
   */
401
  protected Map<String, String> createReferences() {
402
    return new HashMap<>();
403
  }
404
405
  private class ResolverYAMLFactory extends YAMLFactory {
406
407
    @Override
408
    protected YAMLGenerator _createGenerator(
409
      final Writer out, final IOContext ctxt ) throws IOException {
410
411
      return new ResolverYAMLGenerator(
412
        ctxt, _generatorFeatures, _yamlGeneratorFeatures, _objectCodec,
413
        out, _version );
414
    }
415
  }
416
417
  private class ResolverYAMLGenerator extends YAMLGenerator {
418
419
    public ResolverYAMLGenerator(
420
      final IOContext ctxt,
421
      final int jsonFeatures,
422
      final int yamlFeatures,
423
      final ObjectCodec codec,
424
      final Writer out,
425
      final DumperOptions.Version version ) throws IOException {
426
427
      super( ctxt, jsonFeatures, yamlFeatures, codec, out, version );
428
    }
429
430
    @Override
431
    public void writeString( final String text )
432
      throws IOException, JsonGenerationException {
433
      super.writeString( substitute( text ) );
434
    }
435
  }
436
437
  private YAMLFactory getYAMLFactory() {
438
    return new ResolverYAMLFactory();
439
  }
440
441
  private ObjectMapper getObjectMapper() {
442
    return new ObjectMapper( getYAMLFactory() );
443
  }
444
445
  /**
446
   * Returns the character used to separate YAML paths within delimited
447
   * references. This will return only the first character of the command line
448
   * parameter, if the default is overridden.
449
   *
450
   * @return A period by default.
451
   */
452
  private char getDelimitedSeparator() {
453
    return SEPARATOR.charAt( 0 );
454
  }
455
}
1456
A src/main/java/com/scrivenvar/definition/yaml/YamlTreeAdapter.java
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.yaml;
29
30
import com.fasterxml.jackson.databind.JsonNode;
31
import com.scrivenvar.definition.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 Root TreeItem node name.
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
}
1132
A src/main/java/com/scrivenvar/dialogs/ImageDialog.java
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( "![%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) )
78
      .otherwise( Bindings.format( "![%s](%s)", 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
}
1154
A src/main/java/com/scrivenvar/dialogs/ImageDialog.jfd
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
}
187
A src/main/java/com/scrivenvar/dialogs/LinkDialog.java
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.editors.markdown.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
}
1139
A src/main/java/com/scrivenvar/dialogs/LinkDialog.jfd
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
}
192
A src/main/java/com/scrivenvar/editors/EditorPane.java
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.editors;
29
30
import com.scrivenvar.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.SimpleObjectProperty;
36
import javafx.beans.value.ChangeListener;
37
import javafx.event.Event;
38
import javafx.scene.control.ScrollPane;
39
import javafx.scene.input.InputEvent;
40
import org.fxmisc.flowless.VirtualizedScrollPane;
41
import org.fxmisc.richtext.StyleClassedTextArea;
42
import org.fxmisc.undo.UndoManager;
43
import org.fxmisc.wellbehaved.event.EventPattern;
44
import org.fxmisc.wellbehaved.event.InputMap;
45
import static org.fxmisc.wellbehaved.event.InputMap.consume;
46
import org.fxmisc.wellbehaved.event.Nodes;
47
48
/**
49
 * Represents common editing features for various types of text editors.
50
 *
51
 * @author White Magic Software, Ltd.
52
 */
53
public class EditorPane extends AbstractPane {
54
55
  private StyleClassedTextArea editor;
56
  private VirtualizedScrollPane<StyleClassedTextArea> scrollPane;
57
  private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
58
59
  /**
60
   * Set when entering variable edit mode; retrieved upon exiting.
61
   */
62
  private InputMap<InputEvent> nodeMap;
63
64
  @Override
65
  public void requestFocus() {
66
    Platform.runLater( () -> getEditor().requestFocus() );
67
  }
68
69
  public void undo() {
70
    getUndoManager().undo();
71
  }
72
73
  public void redo() {
74
    getUndoManager().redo();
75
  }
76
77
  public UndoManager getUndoManager() {
78
    return getEditor().getUndoManager();
79
  }
80
81
  public String getText() {
82
    return getEditor().getText();
83
  }
84
85
  public void setText( final String text ) {
86
    getEditor().deselect();
87
    getEditor().replaceText( text );
88
    getUndoManager().mark();
89
  }
90
91
  /**
92
   * Call to hook into changes to the text area.
93
   *
94
   * @param listener Receives editor text change events.
95
   */
96
  public void addTextChangeListener( final ChangeListener<? super String> listener ) {
97
    getEditor().textProperty().addListener( listener );
98
  }
99
100
  /**
101
   * Call to listen for when the caret moves to another paragraph.
102
   *
103
   * @param listener Receives paragraph change events.
104
   */
105
  public void addCaretParagraphListener(
106
    final ChangeListener<? super Integer> listener ) {
107
    getEditor().currentParagraphProperty().addListener( listener );
108
  }
109
110
  /**
111
   * This method adds listeners to editor events.
112
   *
113
   * @param <T> The event type.
114
   * @param <U> The consumer type for the given event type.
115
   * @param event The event of interest.
116
   * @param consumer The method to call when the event happens.
117
   */
118
  public <T extends Event, U extends T> void addEventListener(
119
    final EventPattern<? super T, ? extends U> event,
120
    final Consumer<? super U> consumer ) {
121
    Nodes.addInputMap( getEditor(), consume( event, consumer ) );
122
  }
123
124
  /**
125
   * This method adds listeners to editor events that can be removed without
126
   * affecting the original listeners (i.e., the original lister is restored on
127
   * a call to removeEventListener).
128
   *
129
   * @param map The map of methods to events.
130
   */
131
  @SuppressWarnings( "unchecked" )
132
  public void addEventListener( final InputMap<InputEvent> map ) {
133
    this.nodeMap = (InputMap<InputEvent>)getInputMap();
134
    Nodes.addInputMap( getEditor(), map );
135
  }
136
137
  /**
138
   * This method removes listeners to editor events and restores the default
139
   * handler.
140
   *
141
   * @param map The map of methods to events.
142
   */
143
  public void removeEventListener( final InputMap<InputEvent> map ) {
144
    Nodes.removeInputMap( getEditor(), map );
145
    Nodes.addInputMap( getEditor(), this.nodeMap );
146
  }
147
148
  /**
149
   * Returns the value for "org.fxmisc.wellbehaved.event.inputmap".
150
   *
151
   * @return An input map of input events.
152
   */
153
  private Object getInputMap() {
154
    return getEditor().getProperties().get( getInputMapKey() );
155
  }
156
157
  /**
158
   * Returns the hashmap key entry for the input map.
159
   *
160
   * @return "org.fxmisc.wellbehaved.event.inputmap"
161
   */
162
  private String getInputMapKey() {
163
    return "org.fxmisc.wellbehaved.event.inputmap";
164
  }
165
166
  public void scrollToTop() {
167
    getEditor().moveTo( 0 );
168
  }
169
170
  private void setEditor( StyleClassedTextArea textArea ) {
171
    this.editor = textArea;
172
  }
173
174
  public synchronized StyleClassedTextArea getEditor() {
175
    if( this.editor == null ) {
176
      setEditor( createTextArea() );
177
    }
178
179
    return this.editor;
180
  }
181
182
  /**
183
   * Returns the scroll pane that contains the text area.
184
   *
185
   * @return The scroll pane that contains the content to edit.
186
   */
187
  public synchronized VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() {
188
    if( this.scrollPane == null ) {
189
      this.scrollPane = createScrollPane();
190
    }
191
192
    return this.scrollPane;
193
  }
194
195
  protected VirtualizedScrollPane<StyleClassedTextArea> createScrollPane() {
196
    final VirtualizedScrollPane<StyleClassedTextArea> pane
197
      = new VirtualizedScrollPane<>( getEditor() );
198
    pane.setVbarPolicy( ScrollPane.ScrollBarPolicy.ALWAYS );
199
200
    return pane;
201
  }
202
203
  protected StyleClassedTextArea createTextArea() {
204
    return new StyleClassedTextArea( false );
205
  }
206
207
  public Path getPath() {
208
    return this.path.get();
209
  }
210
211
  public void setPath( final Path path ) {
212
    this.path.set( path );
213
  }
214
215
  public ObjectProperty<Path> pathProperty() {
216
    return this.path;
217
  }
218
}
1219
A src/main/java/com/scrivenvar/editors/VariableNameInjector.java
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.editors;
29
30
import com.scrivenvar.FileEditorTabPane;
31
import com.scrivenvar.Services;
32
import com.scrivenvar.decorators.VariableDecorator;
33
import com.scrivenvar.decorators.YamlVariableDecorator;
34
import com.scrivenvar.definition.DefinitionPane;
35
import com.scrivenvar.definition.VariableTreeItem;
36
import static com.scrivenvar.definition.yaml.YamlParser.SEPARATOR;
37
import com.scrivenvar.service.Settings;
38
import static com.scrivenvar.util.Lists.getFirst;
39
import static com.scrivenvar.util.Lists.getLast;
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
}
1843
A src/main/java/com/scrivenvar/editors/markdown/HyperlinkModel.java
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.editors.markdown;
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
}
1145
A src/main/java/com/scrivenvar/editors/markdown/LinkVisitor.java
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.editors.markdown;
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
}
199
A src/main/java/com/scrivenvar/editors/markdown/MarkdownEditorPane.java
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.editors.markdown;
29
30
import static com.scrivenvar.Constants.STYLESHEET_MARKDOWN;
31
import com.scrivenvar.dialogs.ImageDialog;
32
import com.scrivenvar.dialogs.LinkDialog;
33
import com.scrivenvar.editors.EditorPane;
34
import com.scrivenvar.processors.MarkdownProcessor;
35
import static com.scrivenvar.util.Utils.ltrim;
36
import static com.scrivenvar.util.Utils.rtrim;
37
import com.vladsch.flexmark.ast.Link;
38
import com.vladsch.flexmark.ast.Node;
39
import java.nio.file.Path;
40
import java.util.regex.Matcher;
41
import java.util.regex.Pattern;
42
import javafx.beans.value.ObservableValue;
43
import javafx.scene.control.Dialog;
44
import javafx.scene.control.IndexRange;
45
import static javafx.scene.input.KeyCode.ENTER;
46
import javafx.scene.input.KeyEvent;
47
import javafx.stage.Window;
48
import org.fxmisc.richtext.StyleClassedTextArea;
49
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
50
51
/**
52
 * Markdown editor pane.
53
 *
54
 * @author Karl Tauber and White Magic Software, Ltd.
55
 */
56
public class MarkdownEditorPane extends EditorPane {
57
58
  private static final Pattern AUTO_INDENT_PATTERN = Pattern.compile(
59
    "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" );
60
61
  public MarkdownEditorPane() {
62
    initEditor();
63
  }
64
65
  private void initEditor() {
66
    final StyleClassedTextArea textArea = getEditor();
67
68
    textArea.setWrapText( true );
69
    textArea.getStyleClass().add( "markdown-editor" );
70
    textArea.getStylesheets().add(STYLESHEET_MARKDOWN );
71
72
    addEventListener( keyPressed( ENTER ), this::enterPressed );
73
74
    // TODO: Wait for implementation that allows cutting lines, not paragraphs.
75
//    addEventListener( keyPressed( X, SHORTCUT_DOWN ), this::cutLine );
76
  }
77
78
  public ObservableValue<String> markdownProperty() {
79
    return getEditor().textProperty();
80
  }
81
82
  private void enterPressed( final KeyEvent e ) {
83
    final StyleClassedTextArea textArea = getEditor();
84
    final String currentLine = textArea.getText( textArea.getCurrentParagraph() );
85
    final Matcher matcher = AUTO_INDENT_PATTERN.matcher( currentLine );
86
87
    String newText = "\n";
88
89
    if( matcher.matches() ) {
90
      if( !matcher.group( 2 ).isEmpty() ) {
91
        // indent new line with same whitespace characters and list markers as current line
92
        newText = newText.concat( matcher.group( 1 ) );
93
      } else {
94
        // current line contains only whitespace characters and list markers
95
        // --> empty current line
96
        final int caretPosition = textArea.getCaretPosition();
97
        textArea.selectRange( caretPosition - currentLine.length(), caretPosition );
98
      }
99
    }
100
101
    textArea.replaceSelection( newText );
102
  }
103
104
  public void surroundSelection( final String leading, final String trailing ) {
105
    surroundSelection( leading, trailing, null );
106
  }
107
108
  public void surroundSelection( String leading, String trailing, final String hint ) {
109
    final StyleClassedTextArea textArea = getEditor();
110
111
    // Note: not using textArea.insertText() to insert leading and trailing
112
    // because this would add two changes to undo history
113
    final IndexRange selection = textArea.getSelection();
114
    int start = selection.getStart();
115
    int end = selection.getEnd();
116
117
    final String selectedText = textArea.getSelectedText();
118
119
    // remove leading and trailing whitespaces from selected text
120
    String trimmedSelectedText = selectedText.trim();
121
    if( trimmedSelectedText.length() < selectedText.length() ) {
122
      start += selectedText.indexOf( trimmedSelectedText );
123
      end = start + trimmedSelectedText.length();
124
    }
125
126
    // remove leading whitespaces from leading text if selection starts at zero
127
    if( start == 0 ) {
128
      leading = ltrim( leading );
129
    }
130
131
    // remove trailing whitespaces from trailing text if selection ends at text end
132
    if( end == textArea.getLength() ) {
133
      trailing = rtrim( trailing );
134
    }
135
136
    // remove leading line separators from leading text
137
    // if there are line separators before the selected text
138
    if( leading.startsWith( "\n" ) ) {
139
      for( int i = start - 1; i >= 0 && leading.startsWith( "\n" ); i-- ) {
140
        if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
141
          break;
142
        }
143
        leading = leading.substring( 1 );
144
      }
145
    }
146
147
    // remove trailing line separators from trailing or leading text
148
    // if there are line separators after the selected text
149
    final boolean trailingIsEmpty = trailing.isEmpty();
150
    String str = trailingIsEmpty ? leading : trailing;
151
152
    if( str.endsWith( "\n" ) ) {
153
      final int length = textArea.getLength();
154
155
      for( int i = end; i < length && str.endsWith( "\n" ); i++ ) {
156
        if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
157
          break;
158
        }
159
160
        str = str.substring( 0, str.length() - 1 );
161
      }
162
163
      if( trailingIsEmpty ) {
164
        leading = str;
165
      } else {
166
        trailing = str;
167
      }
168
    }
169
170
    int selStart = start + leading.length();
171
    int selEnd = end + leading.length();
172
173
    // insert hint text if selection is empty
174
    if( hint != null && trimmedSelectedText.isEmpty() ) {
175
      trimmedSelectedText = hint;
176
      selEnd = selStart + hint.length();
177
    }
178
179
    // prevent undo merging with previous text entered by user
180
    getUndoManager().preventMerge();
181
182
    // replace text and update selection
183
    textArea.replaceText( start, end, leading + trimmedSelectedText + trailing );
184
    textArea.selectRange( selStart, selEnd );
185
  }
186
187
  /**
188
   * Returns one of: selected text, word under cursor, or parsed hyperlink from
189
   * the markdown AST.
190
   *
191
   * @return
192
   */
193
  private HyperlinkModel getHyperlink() {
194
    final StyleClassedTextArea textArea = getEditor();
195
    final String selectedText = textArea.getSelectedText();
196
197
    // Get the current paragraph, convert to Markdown nodes.
198
    final MarkdownProcessor mp = new MarkdownProcessor( null );
199
    final int p = textArea.getCurrentParagraph();
200
    final String paragraph = textArea.getText( p );
201
    final Node node = mp.toNode( paragraph );
202
    final LinkVisitor visitor = new LinkVisitor( textArea.getCaretColumn() );
203
    final Link link = visitor.process( node );
204
205
    if( link != null ) {
206
      textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() );
207
    }
208
209
    final HyperlinkModel model = createHyperlinkModel(
210
      link, selectedText, "https://website.com"
211
    );
212
213
    return model;
214
  }
215
216
  private HyperlinkModel createHyperlinkModel(
217
    final Link link, final String selection, final String url ) {
218
219
    return link == null
220
      ? new HyperlinkModel( selection, url )
221
      : new HyperlinkModel( link );
222
  }
223
224
  private Path getParentPath() {
225
    final Path parentPath = getPath();
226
    return (parentPath != null) ? parentPath.getParent() : null;
227
  }
228
229
  private Dialog<String> createLinkDialog() {
230
    return new LinkDialog( getWindow(), getHyperlink(), getParentPath() );
231
  }
232
233
  private Dialog<String> createImageDialog() {
234
    return new ImageDialog( getWindow(), getParentPath() );
235
  }
236
237
  private void insertObject( final Dialog<String> dialog ) {
238
    dialog.showAndWait().ifPresent( result -> {
239
      getEditor().replaceSelection( result );
240
    } );
241
  }
242
243
  public void insertLink() {
244
    insertObject( createLinkDialog() );
245
  }
246
247
  public void insertImage() {
248
    insertObject( createImageDialog() );
249
  }
250
251
  private Window getWindow() {
252
    return getScrollPane().getScene().getWindow();
253
  }
254
}
1255
A src/main/java/com/scrivenvar/predicates/files/FileTypePredicate.java
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
}
186
A src/main/java/com/scrivenvar/predicates/strings/ContainsPredicate.java
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
}
164
A src/main/java/com/scrivenvar/predicates/strings/EqualPredicate.java
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
}
158
A src/main/java/com/scrivenvar/predicates/strings/StartsPredicate.java
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
   * Constructs a new instance using a comparate that will be compared with
39
   * the comparator during the test.
40
   *
41
   * @param comparate The string to compare against the comparator.
42
   */
43
  public StartsPredicate( final String comparate ) {
44
    super( comparate );
45
  }
46
47
  /**
48
   * Compares two strings.
49
   *
50
   * @param comparator A non-null string, possibly empty.
51
   *
52
   * @return true The comparator starts with the comparate, ignoring case.
53
   */
54
  @Override
55
  public boolean test( final String comparator ) {
56
    final String comparate = getComparate().toLowerCase();
57
    return comparator.startsWith( comparate.toLowerCase() );
58
  }
59
}
160
A src/main/java/com/scrivenvar/predicates/strings/StringPredicate.java
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
}
149
A src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
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_BASE;
31
import static com.scrivenvar.Constants.STYLESHEET_PREVIEW;
32
import java.nio.file.Path;
33
import javafx.beans.value.ObservableValue;
34
import javafx.concurrent.Worker.State;
35
import static javafx.concurrent.Worker.State.SUCCEEDED;
36
import javafx.scene.Node;
37
import javafx.scene.layout.Pane;
38
import javafx.scene.web.WebEngine;
39
import javafx.scene.web.WebView;
40
41
/**
42
 * HTML preview pane is responsible for rendering an HTML document.
43
 *
44
 * @author Karl Tauber and White Magic Software, Ltd.
45
 */
46
public final class HTMLPreviewPane extends Pane {
47
48
  private final WebView webView = new WebView();
49
  private Path path;
50
51
  /**
52
   * Creates a new preview pane that can scroll to the caret position within the
53
   * document.
54
   */
55
  public HTMLPreviewPane() {
56
    initListeners();
57
    initTraversal();
58
  }
59
60
  /**
61
   * Initializes observers for document changes. When the document is reloaded
62
   * with new HTML, this triggers a scroll event that repositions the document
63
   * to the injected caret (that corresponds with the position in the text
64
   * editor).
65
   */
66
  private void initListeners() {
67
    // Scrolls to the caret after the content has been loaded.
68
    getEngine().getLoadWorker().stateProperty().addListener(
69
      (ObservableValue<? extends State> observable,
70
        final State oldValue, final State newValue) -> {
71
        if( newValue == SUCCEEDED ) {
72
          scrollToCaret();
73
        }
74
      } );
75
  }
76
77
  /**
78
   * Ensures images can be found relative to the document.
79
   *
80
   * @return The base path element to use for the document, or the empty string
81
   * if no path has been set, yet.
82
   */
83
  private String getBase() {
84
    final Path basePath = getPath();
85
86
    return basePath == null
87
      ? ""
88
      : ("<base href='" + basePath.getParent().toUri().toString() + "'>");
89
  }
90
91
  /**
92
   * Updates the internal HTML source, loads it into the preview pane, then
93
   * scrolls to the caret position.
94
   *
95
   * @param html The new HTML document to display.
96
   */
97
  public void update( final String html ) {
98
    getEngine().loadContent(
99
      "<!DOCTYPE html>"
100
      + "<html>"
101
      + "<head>"
102
      + "<link rel='stylesheet' href='" + getClass().getResource( STYLESHEET_PREVIEW ) + "'>"
103
      + getBase()
104
      + "</head>"
105
      + "<body>"
106
      + html
107
      + "</body>"
108
      + "</html>" );
109
  }
110
111
  /**
112
   * Clears out the HTML content from the preview.
113
   */
114
  public void clear() {
115
    update( "" );
116
  }
117
118
  /**
119
   * Scrolls to the caret position in the document.
120
   */
121
  private void scrollToCaret() {
122
    execute( getScrollScript() );
123
  }
124
125
  /**
126
   * Returns the JavaScript used to scroll the WebView pane.
127
   *
128
   * @return A script that tries to center the view port on the CARET POSITION.
129
   */
130
  private String getScrollScript() {
131
    return ""
132
      + "var e = document.getElementById('" + CARET_POSITION_BASE + "');"
133
      + "if( e != null ) { "
134
      + "  Element.prototype.topOffset = function () {"
135
      + "    return this.offsetTop + (this.offsetParent ? this.offsetParent.topOffset() : 0);"
136
      + "  };"
137
      + "  window.scrollTo( 0, e.topOffset() - (window.innerHeight / 2 ) );"
138
      + "}";
139
  }
140
141
  /**
142
   * Prevent tabbing into the preview pane.
143
   */
144
  private void initTraversal() {
145
    getWebView().setFocusTraversable( false );
146
  }
147
148
  private Object execute( final String script ) {
149
    return getEngine().executeScript( script );
150
  }
151
152
  private WebEngine getEngine() {
153
    return getWebView().getEngine();
154
  }
155
156
  private WebView getWebView() {
157
    return this.webView;
158
  }
159
160
  private Path getPath() {
161
    return this.path;
162
  }
163
164
  public void setPath( final Path path ) {
165
    this.path = path;
166
  }
167
168
  /**
169
   * Content to embed in a panel.
170
   *
171
   * @return The content to display to the user.
172
   */
173
  public Node getNode() {
174
    return getWebView();
175
  }
176
}
1177
A src/main/java/com/scrivenvar/processors/AbstractProcessor.java
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
}
183
A src/main/java/com/scrivenvar/processors/HTMLPreviewProcessor.java
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
}
180
A src/main/java/com/scrivenvar/processors/MarkdownCaretInsertionProcessor.java
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_MD;
31
import static java.lang.Character.isLetter;
32
import static java.lang.Math.min;
33
34
/**
35
 * Responsible for inserting the magic CARET POSITION into the markdown so that,
36
 * upon rendering into HTML, the HTML pane can scroll to the correct position
37
 * (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 final int caretPosition;
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 position The caret's current position in the text, cannot be null.
50
   */
51
  public MarkdownCaretInsertionProcessor(
52
    final Processor<String> processor, final int position ) {
53
    super( processor );
54
    this.caretPosition = position;
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
    final int length = t.length();
69
    int offset = min( getCaretPosition(), length );
70
71
    // TODO: Ensure that the caret position is outside of an element, 
72
    // so that a caret inserted in the image doesn't corrupt it. Such as:
73
    //
74
    // ![Screenshot](images/scr|eenshot.png)
75
    //
76
    // 1. Scan back to the previous EOL, which will be the MD AST start point.
77
    // 2. Scan forward until EOF or EOL, which will be the MD AST ending point.
78
    // 3. Convert the text between start and end into MD AST.
79
    // 4. Find the nearest text node to the caret.
80
    // 5. Insert the CARET_POSITION_MD value in the text at that offsset.
81
82
    // Insert the caret at the closest non-Markdown delimiter (i.e., the 
83
    // closest character from the caret position forward).
84
    while( offset < length && !isLetter( t.charAt( offset ) ) ) {
85
      offset++;
86
    }
87
88
    // Insert the caret position into the Markdown text, but don't interfere
89
    // with the Markdown iteself.
90
    return new StringBuilder( t ).replace(
91
      offset, offset, CARET_POSITION_MD ).toString();
92
  }
93
94
  /**
95
   * Returns the editor's caret position.
96
   *
97
   * @return Where the user has positioned the caret.
98
   */
99
  private int getCaretPosition() {
100
    return this.caretPosition;
101
  }
102
}
1103
A src/main/java/com/scrivenvar/processors/MarkdownCaretReplacementProcessor.java
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_HTML;
31
import static com.scrivenvar.Constants.CARET_POSITION_MD;
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
  public MarkdownCaretReplacementProcessor( final Processor<String> processor ) {
43
    super( processor );
44
  }
45
46
  /**
47
   * Replaces each MD_CARET_POSITION with an HTML element that has an id
48
   * attribute of CARET_POSITION. This should only replace one item.
49
   *
50
   * @param t The text that contains
51
   *
52
   * @return
53
   */
54
  @Override
55
  public String processLink( final String t ) {
56
    return replace(t, CARET_POSITION_MD, CARET_POSITION_HTML );
57
  }
58
59
  /**
60
   * Replaces the needle with thread in the given haystack. Based on Apache
61
   * Commons 3 StringUtils.replace method. Should be faster than
62
   * String.replace, which performs a little regex under the hood.
63
   *
64
   * @param haystack Search this string for the needle, must not be null.
65
   * @param needle The text to find in the haystack.
66
   * @param thread Replace the needle with this text, if the needle is found.
67
   *
68
   * @return The haystack with the first instance of needle replaced with
69
   * thread.
70
   */
71
  private static String replace(
72
    final String haystack, final String needle, final String thread ) {
73
74
    final int end = haystack.indexOf( needle, 0 );
75
76
    if( end == INDEX_NOT_FOUND ) {
77
      return haystack;
78
    }
79
80
    int start = 0;
81
    final int needleLength = needle.length();
82
83
    int increase = thread.length() - needleLength;
84
    increase = (increase < 0 ? 0 : increase);
85
    final StringBuilder buffer = new StringBuilder( haystack.length() + increase );
86
87
    if( end != INDEX_NOT_FOUND ) {
88
      buffer.append( haystack.substring( start, end ) ).append( thread );
89
      start = end + needleLength;
90
    }
91
92
    return buffer.append( haystack.substring( start ) ).toString();
93
  }
94
}
195
A src/main/java/com/scrivenvar/processors/MarkdownProcessor.java
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
}
1149
A src/main/java/com/scrivenvar/processors/Processor.java
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
   */
45
  public void processChain( T t );
46
47
  /**
48
   * Processes the given content providing a transformation from one document
49
   * format into another. For example, this could convert from XML to text using
50
   * an XSLT processor, or from markdown to HTML.
51
   *
52
   * @param t The type of object to process.
53
   *
54
   * @return The post-processed document, or null if processing should stop.
55
   */
56
  public T processLink( T t );
57
58
  /**
59
   * Adds a document processor to call after this processor finishes processing
60
   * the document given to the process method.
61
   *
62
   * @return The processor that should transform the document after this
63
   * instance has finished processing.
64
   */
65
  public Processor<T> next();
66
}
167
A src/main/java/com/scrivenvar/processors/VariableProcessor.java
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
}
182
A src/main/java/com/scrivenvar/processors/text/AbstractTextReplacer.java
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
}
149
A src/main/java/com/scrivenvar/processors/text/AhoCorasickReplacer.java
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
}
175
A src/main/java/com/scrivenvar/processors/text/StringUtilsReplacer.java
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.lang3.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
}
151
A src/main/java/com/scrivenvar/processors/text/TextReplacementFactory.java
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
}
156
A src/main/java/com/scrivenvar/processors/text/TextReplacer.java
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
}
151
A src/main/java/com/scrivenvar/service/Configuration.java
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
}
140
A src/main/java/com/scrivenvar/service/Options.java
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
32
/**
33
 * Responsible for persistent options.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public interface Options {
38
39
  public Preferences getState();
40
  
41
  /**
42
   * Stores the key and value into the user preferences to be loaded the next
43
   * time the application is launched.
44
   *
45
   * @param key Name of the key to persist along with its value.
46
   * @param value Value to associate with the key.
47
   */
48
  public void put( String key, String value );
49
50
  /**
51
   * Retrieves the value for a key in the user preferences.
52
   *
53
   * @param key Retrieve the value of this key.
54
   * @param defaultValue The value to return in the event that the given key has
55
   * no associated value.
56
   *
57
   * @return The value associated with the key.
58
   */
59
  public String get( String key, String defaultValue );
60
}
161
A src/main/java/com/scrivenvar/service/Service.java
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
}
137
A src/main/java/com/scrivenvar/service/Settings.java
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.Iterator;
31
import java.util.List;
32
33
/**
34
 * Defines how settings and options can be retrieved.
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public interface Settings extends Service {
39
40
  /**
41
   * Returns a setting property or its default value.
42
   *
43
   * @param property The property key name to obtain its value.
44
   * @param defaultValue The default value to return iff the property cannot be
45
   * found.
46
   *
47
   * @return The property value for the given property key.
48
   */
49
  public String getSetting( String property, String defaultValue );
50
51
  /**
52
   * Returns a setting property or its default value.
53
   *
54
   * @param property The property key name to obtain its value.
55
   * @param defaultValue The default value to return iff the property cannot be
56
   * found.
57
   *
58
   * @return The property value for the given property key.
59
   */
60
  public int getSetting( String property, int defaultValue );
61
62
  /**
63
   * Returns a list of property names that begin with the given prefix. The
64
   * prefix is included in any matching results. This will return keys that
65
   * either match the prefix or start with the prefix followed by a dot ('.').
66
   * For example, a prefix value of <code>the.property.name</code> will likely
67
   * return the expected results, but <code>the.property.name.</code> (note the
68
   * extraneous period) will probably not.
69
   *
70
   * @param prefix The prefix to compare against each property name.
71
   *
72
   * @return The list of property names that have the given prefix.
73
   */
74
  public Iterator<String> getKeys( final String prefix );
75
76
  /**
77
   * Convert the generic list of property objects into strings.
78
   *
79
   * @param property The property value to coerce.
80
   * @param defaults The defaults values to use should the property be unset.
81
   *
82
   * @return The list of properties coerced from objects to strings.
83
   */
84
  public List<String> getStringSettingList( String property, List<String> defaults );
85
86
  /**
87
   * Converts the generic list of property objects into strings.
88
   *
89
   * @param property The property value to coerce.
90
   *
91
   * @return The list of properties coerced from objects to strings.
92
   */
93
  public List<String> getStringSettingList( String property );
94
}
195
A src/main/java/com/scrivenvar/service/events/AlertMessage.java
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
}
150
A src/main/java/com/scrivenvar/service/events/AlertService.java
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
}
184
A src/main/java/com/scrivenvar/service/events/impl/ButtonOrderPane.java
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
}
164
A src/main/java/com/scrivenvar/service/events/impl/DefaultAlertMessage.java
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
}
167
A src/main/java/com/scrivenvar/service/events/impl/DefaultAlertService.java
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
}
1100
A src/main/java/com/scrivenvar/service/events/impl/FileType.java
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
}
145
A src/main/java/com/scrivenvar/service/impl/DefaultOptions.java
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 static com.scrivenvar.Constants.PREFS_ROOT;
30
import com.scrivenvar.service.Options;
31
import java.util.prefs.Preferences;
32
import static java.util.prefs.Preferences.userRoot;
33
import static com.scrivenvar.Constants.PREFS_STATE;
34
import static com.scrivenvar.Constants.PREFS_OPTIONS;
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 Preferences preferences;
43
  
44
  public DefaultOptions() {
45
    setPreferences(getRootPreferences().node(PREFS_OPTIONS ) );
46
  }
47
48
  @Override
49
  public void put( final String key, final String value ) {
50
    getPreferences().put( key, value );
51
  }
52
  
53
  @Override
54
  public String get( final String key, final String defalutValue ) {
55
    return getPreferences().get( key, defalutValue );
56
  }
57
  
58
  private void setPreferences( final Preferences preferences ) {
59
    this.preferences = preferences;
60
  }
61
62
  private Preferences getRootPreferences() {
63
    return userRoot().node( PREFS_ROOT );
64
  }
65
66
  @Override
67
  public Preferences getState() {
68
    return getRootPreferences().node(PREFS_STATE );
69
  }
70
71
  private Preferences getPreferences() {
72
    return this.preferences;
73
  }
74
}
175
A src/main/java/com/scrivenvar/service/impl/DefaultSettings.java
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.io.InputStreamReader;
34
import java.io.Reader;
35
import java.net.URISyntaxException;
36
import java.net.URL;
37
import java.util.Iterator;
38
import java.util.List;
39
import org.apache.commons.configuration2.PropertiesConfiguration;
40
import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
41
import org.apache.commons.configuration2.convert.ListDelimiterHandler;
42
import org.apache.commons.configuration2.ex.ConfigurationException;
43
44
/**
45
 * Responsible for loading settings that help avoid hard-coded assumptions.
46
 *
47
 * @author White Magic Software, Ltd.
48
 */
49
public class DefaultSettings implements Settings {
50
  private static final char VALUE_SEPARATOR = ',';
51
52
  private PropertiesConfiguration properties;
53
54
  public DefaultSettings()
55
    throws ConfigurationException, URISyntaxException, IOException {
56
    setProperties( createProperties() );
57
  }
58
59
  /**
60
   * Returns the value of a string property.
61
   *
62
   * @param property The property key.
63
   * @param defaultValue The value to return if no property key has been set.
64
   *
65
   * @return The property key value, or defaultValue when no key found.
66
   */
67
  @Override
68
  public String getSetting( final String property, final String defaultValue ) {
69
    return getSettings().getString( property, defaultValue );
70
  }
71
72
  /**
73
   * Returns the value of a string property.
74
   *
75
   * @param property The property key.
76
   * @param defaultValue The value to return if no property key has been set.
77
   *
78
   * @return The property key value, or defaultValue when no key found.
79
   */
80
  @Override
81
  public int getSetting( final String property, final int defaultValue ) {
82
    return getSettings().getInt( property, defaultValue );
83
  }
84
85
  /**
86
   * Convert the generic list of property objects into strings.
87
   *
88
   * @param property The property value to coerce.
89
   * @param defaults The defaults values to use should the property be unset.
90
   *
91
   * @return The list of properties coerced from objects to strings.
92
   */
93
  @Override
94
  public List<String> getStringSettingList(
95
    final String property, final List<String> defaults ) {
96
    return getSettings().getList( String.class, property, defaults );
97
  }
98
99
  /**
100
   * Convert a list of property objects into strings, with no default value.
101
   *
102
   * @param property The property value to coerce.
103
   *
104
   * @return The list of properties coerced from objects to strings.
105
   */
106
  @Override
107
  public List<String> getStringSettingList( final String property ) {
108
    return getStringSettingList( property, null );
109
  }
110
111
  /**
112
   * Returns a list of property names that begin with the given prefix.
113
   *
114
   * @param prefix The prefix to compare against each property name.
115
   *
116
   * @return The list of property names that have the given prefix.
117
   */
118
  @Override
119
  public Iterator<String> getKeys( final String prefix ) {
120
    return getSettings().getKeys( prefix );
121
  }
122
123
  private PropertiesConfiguration createProperties()
124
    throws ConfigurationException {
125
126
    final URL url = getPropertySource();
127
    final PropertiesConfiguration configuration = new PropertiesConfiguration();
128
129
    if( url != null ) {
130
      try( final Reader r = new InputStreamReader( url.openStream() ) ) {
131
        configuration.setListDelimiterHandler( createListDelimiterHandler() );
132
        configuration.read( r );
133
134
      } catch( IOException e ) {
135
        throw new ConfigurationException( e );
136
      }
137
    }
138
139
    return configuration;
140
  }
141
  
142
  protected ListDelimiterHandler createListDelimiterHandler() {
143
    return new DefaultListDelimiterHandler( VALUE_SEPARATOR );
144
  }
145
146
  private URL getPropertySource() {
147
    return getClass().getResource( getSettingsFilename() );
148
  }
149
150
  private String getSettingsFilename() {
151
    return SETTINGS_NAME;
152
  }
153
154
  private void setProperties( final PropertiesConfiguration configuration ) {
155
    this.properties = configuration;
156
  }
157
158
  private PropertiesConfiguration getSettings() {
159
    return this.properties;
160
  }
161
}
1162
A src/main/java/com/scrivenvar/test/TestDefinitionPane.java
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.test;
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
}
184
A src/main/java/com/scrivenvar/test/TestHarness.java
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.test;
29
30
import static com.scrivenvar.Messages.get;
31
import com.scrivenvar.definition.DefinitionPane;
32
import com.scrivenvar.definition.yaml.YamlParser;
33
import com.scrivenvar.definition.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
}
1116
A src/main/java/com/scrivenvar/test/TestProperties.java
1
package com.scrivenvar.test;
2
3
import java.io.IOException;
4
import java.io.StringReader;
5
import java.util.Arrays;
6
import org.apache.commons.configuration2.PropertiesConfiguration;
7
import org.apache.commons.configuration2.ex.ConfigurationException;
8
9
public class TestProperties {
10
11
  public static void main( final String args[] ) throws ConfigurationException, IOException {
12
    final String p = ""
13
      + "file.ext.definition.yaml=*.yml,*.yaml\n"
14
      + "filter.file.ext.definition=${file.ext.definition.yaml}\n";
15
16
    try( final StringReader r = new StringReader( p ) ) {
17
18
      PropertiesConfiguration config = new PropertiesConfiguration();
19
      config.read( r );
20
21
      System.out.println( config.getList( "filter.file.ext.definition" ) );
22
      System.out.println( config.getString( "filter.file.ext.definition" ) );
23
      System.out.println( Arrays.toString( config.getStringArray( "filter.file.ext.definition" ) ) );
24
    }
25
  }
26
}
127
A src/main/java/com/scrivenvar/test/TestVariableNameProcessor.java
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.test;
29
30
import com.scrivenvar.definition.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.lang3.RandomStringUtils.randomNumeric;
49
import org.apache.commons.lang3.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
}
1219
A src/main/java/com/scrivenvar/util/Action.java
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
}
165
A src/main/java/com/scrivenvar/util/ActionUtils.java
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
}
1114
A src/main/java/com/scrivenvar/util/Item.java
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
/**
30
 * Simple item for a ChoiceBox, ComboBox or ListView. Consists of a string name
31
 * and a value object. toString() returns the name. equals() compares the value
32
 * and hashCode() returns the hash code of the value.
33
 *
34
 * @author Karl Tauber
35
 * @param <V> The type of item value.
36
 */
37
public class Item<V> {
38
39
  public final String name;
40
  public final V value;
41
42
  public Item( final String name, final V value ) {
43
    this.name = name;
44
    this.value = value;
45
  }
46
47
  @Override
48
  public boolean equals( final Object obj ) {
49
    if( this == obj ) {
50
      return true;
51
    }
52
    if( !(obj instanceof Item) ) {
53
      return false;
54
    }
55
    return Utils.safeEquals( value, ((Item<?>)obj).value );
56
  }
57
58
  @Override
59
  public int hashCode() {
60
    return (value != null) ? value.hashCode() : 0;
61
  }
62
63
  @Override
64
  public String toString() {
65
    return name;
66
  }
67
}
168
A src/main/java/com/scrivenvar/util/Lists.java
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.util;
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
}
1104
A src/main/java/com/scrivenvar/util/StageState.java
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
  public static final String K_PANE_SPLIT_PREVIEW = "pane.split.preview";
45
46
  private final Stage stage;
47
  private final Preferences state;
48
49
  private Rectangle normalBounds;
50
  private boolean runLaterPending;
51
52
  public StageState( final Stage stage, final Preferences state ) {
53
    this.stage = stage;
54
    this.state = state;
55
56
    restore();
57
58
    stage.addEventHandler( WindowEvent.WINDOW_HIDING, e -> save() );
59
60
    stage.xProperty().addListener( (ob, o, n) -> boundsChanged() );
61
    stage.yProperty().addListener( (ob, o, n) -> boundsChanged() );
62
    stage.widthProperty().addListener( (ob, o, n) -> boundsChanged() );
63
    stage.heightProperty().addListener( (ob, o, n) -> boundsChanged() );
64
  }
65
66
  private void save() {
67
    final Rectangle bounds = isNormalState() ? getStageBounds() : normalBounds;
68
    
69
    if( bounds != null ) {
70
      state.putDouble( "windowX", bounds.getX() );
71
      state.putDouble( "windowY", bounds.getY() );
72
      state.putDouble( "windowWidth", bounds.getWidth() );
73
      state.putDouble( "windowHeight", bounds.getHeight() );
74
    }
75
    
76
    state.putBoolean( "windowMaximized", stage.isMaximized() );
77
    state.putBoolean( "windowFullScreen", stage.isFullScreen() );
78
  }
79
80
  private void restore() {
81
    final double x = state.getDouble( "windowX", Double.NaN );
82
    final double y = state.getDouble( "windowY", Double.NaN );
83
    final double w = state.getDouble( "windowWidth", Double.NaN );
84
    final double h = state.getDouble( "windowHeight", Double.NaN );
85
    final boolean maximized = state.getBoolean( "windowMaximized", false );
86
    final boolean fullScreen = state.getBoolean( "windowFullScreen", false );
87
88
    if( !Double.isNaN( x ) && !Double.isNaN( y ) ) {
89
      stage.setX( x );
90
      stage.setY( y );
91
    } // else: default behavior is center on screen
92
93
    if( !Double.isNaN( w ) && !Double.isNaN( h ) ) {
94
      stage.setWidth( w );
95
      stage.setHeight( h );
96
    } // else: default behavior is use scene size
97
98
    if( fullScreen != stage.isFullScreen() ) {
99
      stage.setFullScreen( fullScreen );
100
    }
101
    
102
    if( maximized != stage.isMaximized() ) {
103
      stage.setMaximized( maximized );
104
    }
105
  }
106
107
  /**
108
   * Remembers the window bounds when the window is not iconified, maximized or
109
   * in fullScreen.
110
   */
111
  private void boundsChanged() {
112
    // avoid too many (and useless) runLater() invocations
113
    if( runLaterPending ) {
114
      return;
115
    }
116
    
117
    runLaterPending = true;
118
119
    // must use runLater() to ensure that change of all properties
120
    // (x, y, width, height, iconified, maximized and fullScreen)
121
    // has finished
122
    Platform.runLater( () -> {
123
      runLaterPending = false;
124
125
      if( isNormalState() ) {
126
        normalBounds = getStageBounds();
127
      }
128
    } );
129
  }
130
131
  private boolean isNormalState() {
132
    return !stage.isIconified() && !stage.isMaximized() && !stage.isFullScreen();
133
  }
134
135
  private Rectangle getStageBounds() {
136
    return new Rectangle( stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight() );
137
  }
138
}
1139
A src/main/java/com/scrivenvar/util/Utils.java
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.prefs.Preferences;
31
32
/**
33
 * @author Karl Tauber
34
 */
35
public class Utils {
36
37
  public static boolean safeEquals( final Object o1, final Object o2 ) {
38
    if( o1 == o2 ) {
39
      return true;
40
    }
41
    if( o1 == null || o2 == null ) {
42
      return false;
43
    }
44
    return o1.equals( o2 );
45
  }
46
47
  public static boolean isNullOrEmpty( final String s ) {
48
    return s == null || s.isEmpty();
49
  }
50
51
  public static String ltrim( final String s ) {
52
    int i = 0;
53
54
    while( i < s.length() && Character.isWhitespace( s.charAt( i ) ) ) {
55
      i++;
56
    }
57
58
    return s.substring( i );
59
  }
60
61
  public static String rtrim( final String s ) {
62
    int i = s.length() - 1;
63
64
    while( i >= 0 && Character.isWhitespace( s.charAt( i ) ) ) {
65
      i--;
66
    }
67
68
    return s.substring( 0, i + 1 );
69
  }
70
71
  public static void putPrefs( Preferences prefs, String key, String value, String def ) {
72
    if( value != def && !value.equals( def ) ) {
73
      prefs.put( key, value );
74
    } else {
75
      prefs.remove( key );
76
    }
77
  }
78
79
  public static void putPrefsInt( Preferences prefs, String key, int value, int def ) {
80
    if( value != def ) {
81
      prefs.putInt( key, value );
82
    } else {
83
      prefs.remove( key );
84
    }
85
  }
86
87
  public static void putPrefsBoolean( Preferences prefs, String key, boolean value, boolean def ) {
88
    if( value != def ) {
89
      prefs.putBoolean( key, value );
90
    } else {
91
      prefs.remove( key );
92
    }
93
  }
94
95
  public static String[] getPrefsStrings( final Preferences prefs, String key ) {
96
    final ArrayList<String> arr = new ArrayList<>( 256 );
97
98
    for( int i = 0; i < 10000; i++ ) {
99
      final String s = prefs.get( key + (i + 1), null );
100
101
      if( s == null ) {
102
        break;
103
      }
104
105
      arr.add( s );
106
    }
107
108
    return arr.toArray( new String[ arr.size() ] );
109
  }
110
111
  public static void putPrefsStrings( Preferences prefs, String key, String[] strings ) {
112
    for( int i = 0; i < strings.length; i++ ) {
113
      prefs.put( key + (i + 1), strings[ i ] );
114
    }
115
116
    for( int i = strings.length; prefs.get( key + (i + 1), null ) != null; i++ ) {
117
      prefs.remove( key + (i + 1) );
118
    }
119
  }
120
}
1121
A src/main/resources/META-INF/services/com.scrivenvar.service.Options
1
1
com.scrivenvar.service.impl.DefaultOptions
A src/main/resources/META-INF/services/com.scrivenvar.service.Settings
1
1
com.scrivenvar.service.impl.DefaultSettings
A src/main/resources/META-INF/services/com.scrivenvar.service.events.AlertService
1
1
com.scrivenvar.service.events.impl.DefaultAlertService
A src/main/resources/com/scrivenvar/build.sh
1
#!/bin/bash
2
3
INKSCAPE=/usr/bin/inkscape
4
5
declare -a SIZES=("16" "32" "64" "128" "256" "512")
6
7
for i in "${SIZES[@]}"; do
8
  # -y: export background opacity 0
9
  $INKSCAPE -y 0 -z -f "logo.svg" -w "${i}" -e "logo${i}.png"
10
done
11
112
A src/main/resources/com/scrivenvar/editor/Markdown.css
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
}
1152
A src/main/resources/com/scrivenvar/logo.svg
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>
172
A src/main/resources/com/scrivenvar/logo128.png
Binary file
A src/main/resources/com/scrivenvar/logo16.png
Binary file
A src/main/resources/com/scrivenvar/logo256.png
Binary file
A src/main/resources/com/scrivenvar/logo32.png
Binary file
A src/main/resources/com/scrivenvar/logo512.png
Binary file
A src/main/resources/com/scrivenvar/logo64.png
Binary file
A src/main/resources/com/scrivenvar/messages.properties
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]]")
1247
A src/main/resources/com/scrivenvar/preview/webview.css
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
}
305
306
/* CARET 
307
=============================================================================*/
308
309
#CARETPOSITION {
310
  border-right:1px solid #333;
311
  margin-right:-1px;
312
  animation: blink 1s linear infinite;
313
}
314
315
@keyframes blink {
316
  from {
317
    visibility:hidden;
318
  }
319
  50% {
320
    visibility:hidden;
321
  }
322
  to {
323
    visibility:visible;
324
  }
325
}
1326
A src/main/resources/com/scrivenvar/scene.css
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
}
146
A src/main/resources/com/scrivenvar/settings.properties
1
# ########################################################################
2
#
3
# Application
4
#
5
# ########################################################################
6
7
application.title=scrivenvar
8
application.package=com/${application.title}
9
application.messages= com.${application.title}.messages
10
11
# ########################################################################
12
#
13
# Preferences
14
#
15
# ########################################################################
16
17
preferences.root=com.${application.title}
18
preferences.root.state=state
19
preferences.root.options=options
20
preferences.root.definition.source=definition.source
21
22
# ########################################################################
23
#
24
# File References
25
#
26
# ########################################################################
27
28
file.stylesheet.scene=${application.package}/scene.css
29
file.stylesheet.markdown=${application.package}/editor/Markdown.css
30
file.stylesheet.preview=webview.css
31
32
file.logo.16 =${application.package}/logo16.png
33
file.logo.32 =${application.package}/logo32.png
34
file.logo.128=${application.package}/logo128.png
35
file.logo.256=${application.package}/logo256.png
36
file.logo.512=${application.package}/logo512.png
37
38
# ########################################################################
39
#
40
# Caret token
41
#
42
# ########################################################################
43
caret.token.base=CARETPOSITION
44
caret.token.markdown=%${constant.caret.token.base}%
45
caret.token.xml=<![CDATA[${constant.caret.token.markdown}]]>
46
caret.token.html=<span id="${caret.token.base}"></span>
47
48
# ########################################################################
49
#
50
# Filename Extensions
51
#
52
# ########################################################################
53
54
# Comma-separated list of definition filename extensions.
55
file.ext.definition.json=*.json
56
file.ext.definition.toml=*.toml
57
file.ext.definition.yaml=*.yml,*.yaml
58
file.ext.definition.properties=*.properties,*.props
59
60
# Comma-separated list of filename extensions.
61
filter.file.ext.markdown=*.Rmd,*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt
62
filter.file.ext.definition=${file.ext.definition.yaml}
63
filter.file.ext.xml=*.xml,*.Rxml
64
filter.file.ext.all=*.*
65
66
# ########################################################################
67
#
68
# Variable Name Editor
69
#
70
# ########################################################################
71
72
# Maximum number of characters for a variable name. A variable is defined
73
# as one or more non-whitespace characters up to this maximum length.
74
editor.variable.maxLength=256
75
76
# ########################################################################
77
#
78
# Dialog Preferences
79
#
80
# ########################################################################
81
82
# docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ButtonBar.html
83
dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R
84
dialog.alert.button.order.linux=L_HE+UNYACBXIO_R
85
dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R
86
87
# Ensures a consistent button order for alert dialogs across platforms (because
88
# the default button order on Linux defies all logic). Power to the people.
89
dialog.alert.button.order=${dialog.alert.button.order.windows}
190
A src/main/resources/com/scrivenvar/variables.yaml
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
1442