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
src/main/resources/com/scrivenvar/build.sh
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.5
4
5
- Added document processors for Markdown and Variables
6
- Simplified code base
7
- Added `Ctrl+Space` hot key for quick variable injection
8
- Replaced commonmark-java with flexmark
9
- Added generic CARETPOSITION into document to scroll preview pane
10
11
## 0.4
12
13
- Changed name to Scrivenvar
14
- Added hot-keys for variable mode and autocomplete
15
- Replaced pegdown with commonmark-java
16
- Started document processors to provide XSLT and variable dereferencing
17
18
## 0.3
19
20
- Changed name to Scrivendor
21
- Changed logo to match
22
- Started to implement service-oriented architecture
23
24
## 0.2
25
- RichTextFX (and dependencies) updated to version 0.6.10 (fixes bugs)
26
- pegdown Markdown parser updated to version 1.6
27
- Added five new pegdown 1.6 extension flags to Markdown Options tab
28
- Minor improvements
29
30
## 0.1
31
32
- Initial release
133
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)
111
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
Scrivenvar
4
===
5
6
Word processing with variables.
7
8
![Screenshot](images/screenshot.png)
9
10
Features
11
---
12
13
* User-defined variables
14
* Preview variable values in real time
15
* Platform independent (Windows, Linux, MacOS)
16
* Auto-insert variable names by typing in values followed by `Control+Space`
17
18
Future Features
19
---
20
* Spell check
21
* XML and XSL processing
22
* Search and replace text with variables
23
* R integration using [Rserve](https://rforge.net/Rserve/)
24
25
Requirements
26
---
27
28
Download and install [Java 8u40](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
29
30
Installation
31
---
32
1. [Download](https://github.com/DaveJarvis/scrivenvar/releases) the latest zip archive and extract it to any folder.
33
1. Double-click `scrivenvar.jar` to start the application.
34
35
License
36
---
37
38
This software is licensed under the [BSD 2-Clause License](LICENSE).
139
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: 'commons-configuration', name: 'commons-configuration', version: '1.10'
24
  compile group: 'com.vladsch.flexmark', name: 'flexmark', version: '0.6.1'
25
  compile group: 'com.vladsch.flexmark', name: 'flexmark-ext-gfm-tables', version: '0.6.1'
26
  compile group: 'org.yaml', name: 'snakeyaml', version: '1.17'
27
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.4'
28
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.4'
29
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.8.4'
30
  compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.8.4'
31
  compile group: 'org.ahocorasick', name: 'ahocorasick', version: '0.3.0'
32
  compile group: 'junit', name: 'junit', version: '4.4'
33
}
34
35
jar {
36
	baseName = 'scrivenvar'
37
	manifest {
38
		attributes 'Main-Class': mainClassName,
39
					'Class-Path': configurations.compile.collect { 'lib/' + it.getName() }.join(' ')
40
	}
41
}
42
43
distributions {
44
	main {
45
		baseName = 'scrivenvar'
46
		contents {
47
			from { ['LICENSE', 'README.md'] }
48
			into( 'images' ) {
49
				from { 'images' }
50
			}
51
		}
52
	}
53
}
154
A gradle.properties
11
A images/logo64.png
Binary file
A images/screenshot.png
Binary file
A settings.gradle
11
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
/**
31
 * @author White Magic Software, Ltd.
32
 */
33
public class Constants {
34
35
  /**
36
   * Prevent instantiation.
37
   */
38
  private Constants() {
39
  }
40
  
41
  public static final String BUNDLE_NAME = "com.scrivenvar.messages";
42
  public static final String SETTINGS_NAME = "/com/scrivenvar/settings.properties";
43
44
  public static final String STYLESHEET_PREVIEW = "com/scrivenvar/scene.css";
45
  public static final String STYLESHEET_EDITOR = "com/scrivenvar/editor/Markdown.css";
46
47
  public static final String LOGO_32 = "com/scrivenvar/logo32.png";
48
  public static final String LOGO_16 = "com/scrivenvar/logo16.png";
49
  public static final String LOGO_128 = "com/scrivenvar/logo128.png";
50
  public static final String LOGO_256 = "com/scrivenvar/logo256.png";
51
  public static final String LOGO_512 = "com/scrivenvar/logo512.png";
52
  
53
  /**
54
   * Separates YAML variable nodes (e.g., the dots in <code>$root.node.var$</code>).
55
   */
56
  public static final String SEPARATOR = ".";
57
  
58
  public static final String CARET_POSITION = "CARETPOSITION";
59
  public static final String MD_CARET_POSITION = "${" + CARET_POSITION + "}";
60
  public static final String XML_CARET_POSITION = "<![CDATA[" + MD_CARET_POSITION + "]]>";
61
}
162
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.editor.EditorPane;
29
import com.scrivenvar.editor.MarkdownEditorPane;
30
import com.scrivenvar.preview.HTMLPreviewPane;
31
import com.scrivenvar.service.Options;
32
import com.scrivenvar.service.events.AlertMessage;
33
import com.scrivenvar.service.events.AlertService;
34
import java.io.IOException;
35
import java.nio.file.Files;
36
import java.nio.file.Path;
37
import java.util.function.Consumer;
38
import javafx.application.Platform;
39
import javafx.beans.binding.Bindings;
40
import javafx.beans.property.BooleanProperty;
41
import javafx.beans.property.ReadOnlyBooleanProperty;
42
import javafx.beans.property.ReadOnlyBooleanWrapper;
43
import javafx.beans.property.SimpleBooleanProperty;
44
import javafx.event.Event;
45
import javafx.scene.control.Alert;
46
import javafx.scene.control.SplitPane;
47
import javafx.scene.control.Tab;
48
import javafx.scene.control.Tooltip;
49
import javafx.scene.input.InputEvent;
50
import javafx.scene.text.Text;
51
import org.fxmisc.flowless.VirtualizedScrollPane;
52
import org.fxmisc.richtext.StyleClassedTextArea;
53
import org.fxmisc.undo.UndoManager;
54
import org.fxmisc.wellbehaved.event.EventPattern;
55
import org.fxmisc.wellbehaved.event.InputMap;
56
57
/**
58
 * Editor for a single file.
59
 *
60
 * @author Karl Tauber and White Magic Software, Ltd.
61
 */
62
public final class FileEditorTab extends Tab {
63
64
  private final Options options = Services.load( Options.class );
65
  private final AlertService alertService = Services.load( AlertService.class );
66
67
  private EditorPane editorPane;
68
  private HTMLPreviewPane previewPane;
69
70
  private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper();
71
  private final BooleanProperty canUndo = new SimpleBooleanProperty();
72
  private final BooleanProperty canRedo = new SimpleBooleanProperty();
73
  private Path path;
74
75
  FileEditorTab( final Path path ) {
76
    setPath( path );
77
    setUserData( this );
78
79
    this.modified.addListener( (observable, oldPath, newPath) -> updateTab() );
80
    updateTab();
81
82
    setOnSelectionChanged( e -> {
83
      if( isSelected() ) {
84
        Platform.runLater( () -> activated() );
85
      }
86
    } );
87
  }
88
89
  private void updateTab() {
90
    final Path filePath = getPath();
91
92
    setText( getFilename( filePath ) );
93
    setGraphic( getModifiedMark() );
94
    setTooltip( getTooltip( filePath ) );
95
  }
96
97
  private String getFilename( final Path filePath ) {
98
    return (filePath == null)
99
      ? Messages.get( "FileEditor.untitled" )
100
      : filePath.getFileName().toString();
101
  }
102
103
  private Tooltip getTooltip( final Path filePath ) {
104
    return (filePath == null)
105
      ? null
106
      : new Tooltip( filePath.toString() );
107
  }
108
109
  private Text getModifiedMark() {
110
    return isModified() ? new Text( "*" ) : null;
111
  }
112
113
  /**
114
   * Called when the user switches tab.
115
   */
116
  private void activated() {
117
    if( getTabPane() == null || !isSelected() ) {
118
      // Tab is closed or no longer active.
119
      return;
120
    }
121
122
    if( getContent() != null ) {
123
      getEditorPane().requestFocus();
124
      return;
125
    }
126
127
    // Load the text and update the preview before the undo manager.
128
    load();
129
130
    // Track undo requests (must not be called before load).
131
    initUndoManager();
132
    initSplitPane();
133
  }
134
135
  public void initSplitPane() {
136
    final EditorPane editor = getEditorPane();
137
    final HTMLPreviewPane preview = getPreviewPane();
138
139
    final VirtualizedScrollPane<StyleClassedTextArea> editorScrollPane = editor.getScrollPane();
140
141
    // Make the preview pane scroll correspond to the editor pane scroll.
142
    // Separate the edit and preview panels.
143
    final SplitPane splitPane = new SplitPane(
144
      editorScrollPane,
145
      preview.getWebView() );
146
    setContent( splitPane );
147
148
    // Let the user edit.
149
    editor.requestFocus();
150
  }
151
152
  private void initUndoManager() {
153
    final UndoManager undoManager = getUndoManager();
154
155
    // Clear undo history after first load.
156
    undoManager.forgetHistory();
157
158
    // Bind the editor undo manager to the properties.
159
    modified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) );
160
    canUndo.bind( undoManager.undoAvailableProperty() );
161
    canRedo.bind( undoManager.redoAvailableProperty() );
162
  }
163
164
  /**
165
   * Returns the index into the text where the caret blinks happily away.
166
   *
167
   * @return A number from 0 to the editor's document text length.
168
   */
169
  public int getCaretPosition() {
170
    return getEditorPane().getEditor().getCaretPosition();
171
  }
172
173
  void load() {
174
    final Path filePath = getPath();
175
176
    if( filePath != null ) {
177
      try {
178
        final byte[] bytes = Files.readAllBytes( filePath );
179
        String markdown;
180
181
        try {
182
          markdown = new String( bytes, getOptions().getEncoding() );
183
        } catch( Exception e ) {
184
          // Unsupported encodings and null pointers fallback here.
185
          markdown = new String( bytes );
186
        }
187
188
        getEditorPane().setText( markdown );
189
      } catch( IOException ex ) {
190
        final AlertMessage message = getAlertService().createAlertMessage(
191
          Messages.get( "FileEditor.loadFailed.title" ),
192
          Messages.get( "FileEditor.loadFailed.message" ),
193
          filePath,
194
          ex.getMessage()
195
        );
196
197
        final Alert alert = getAlertService().createAlertError( message );
198
199
        alert.showAndWait();
200
      }
201
    }
202
  }
203
204
  boolean save() {
205
    final String text = getEditorPane().getText();
206
207
    byte[] bytes;
208
209
    try {
210
      bytes = text.getBytes( getOptions().getEncoding() );
211
    } catch( Exception ex ) {
212
      bytes = text.getBytes();
213
    }
214
215
    try {
216
      Files.write( getPath(), bytes );
217
      getEditorPane().getUndoManager().mark();
218
      return true;
219
    } catch( IOException ex ) {
220
      final AlertService service = getAlertService();
221
      final AlertMessage message = service.createAlertMessage(
222
        Messages.get( "FileEditor.saveFailed.title" ),
223
        Messages.get( "FileEditor.saveFailed.message" ),
224
        getPath(),
225
        ex.getMessage()
226
      );
227
228
      final Alert alert = service.createAlertError( message );
229
230
      alert.showAndWait();
231
      return false;
232
    }
233
  }
234
235
  Path getPath() {
236
    return this.path;
237
  }
238
239
  void setPath( final Path path ) {
240
    this.path = path;
241
  }
242
243
  boolean isModified() {
244
    return this.modified.get();
245
  }
246
247
  ReadOnlyBooleanProperty modifiedProperty() {
248
    return this.modified.getReadOnlyProperty();
249
  }
250
251
  BooleanProperty canUndoProperty() {
252
    return this.canUndo;
253
  }
254
255
  BooleanProperty canRedoProperty() {
256
    return this.canRedo;
257
  }
258
259
  private UndoManager getUndoManager() {
260
    return getEditorPane().getUndoManager();
261
  }
262
263
  public <T extends Event, U extends T> void addEventListener(
264
    final EventPattern<? super T, ? extends U> event,
265
    final Consumer<? super U> consumer ) {
266
    getEditorPane().addEventListener( event, consumer );
267
  }
268
269
  public void addEventListener( final InputMap<InputEvent> map ) {
270
    getEditorPane().addEventListener( map );
271
  }
272
273
  public void removeEventListener( final InputMap<InputEvent> map ) {
274
    getEditorPane().removeEventListener( map );
275
  }
276
277
  protected EditorPane getEditorPane() {
278
    if( this.editorPane == null ) {
279
      this.editorPane = new MarkdownEditorPane();
280
    }
281
282
    return this.editorPane;
283
  }
284
285
  private AlertService getAlertService() {
286
    return this.alertService;
287
  }
288
289
  private Options getOptions() {
290
    return this.options;
291
  }
292
293
  public HTMLPreviewPane getPreviewPane() {
294
    if( this.previewPane == null ) {
295
      this.previewPane = new HTMLPreviewPane( getPath() );
296
    }
297
298
    return this.previewPane;
299
  }
300
}
1301
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 class FileEditorTabPane extends TabPane implements ChangeListener<Tab> {
75
76
  private final static String FILTER_PREFIX = "Dialog.file.choose.filter";
77
78
  private final Options options = Services.load( Options.class );
79
  private final Settings settings = Services.load( Settings.class );
80
  private final AlertService alertService = Services.load( AlertService.class );
81
82
  private final ReadOnlyObjectWrapper<FileEditorTab> activeFileEditor = new ReadOnlyObjectWrapper<>();
83
  private final ReadOnlyBooleanWrapper anyFileEditorModified = new ReadOnlyBooleanWrapper();
84
85
  public FileEditorTabPane() {
86
    final ObservableList<Tab> tabs = getTabs();
87
88
    setFocusTraversable( false );
89
    setTabClosingPolicy( TabClosingPolicy.ALL_TABS );
90
91
    // Observe the tab so that when a new tab is opened or selected,
92
    // a notification is kicked off.
93
    getSelectionModel().selectedItemProperty().addListener( this );
94
95
    // update anyFileEditorModified property
96
    final ChangeListener<Boolean> modifiedListener = (observable, oldValue, newValue) -> {
97
      for( final Tab tab : tabs ) {
98
        if( ((FileEditorTab)tab.getUserData()).isModified() ) {
99
          this.anyFileEditorModified.set( true );
100
          break;
101
        }
102
      }
103
    };
104
105
    tabs.addListener( (ListChangeListener<Tab>)change -> {
106
      while( change.next() ) {
107
        if( change.wasAdded() ) {
108
          change.getAddedSubList().stream().forEach( (tab) -> {
109
            ((FileEditorTab)tab.getUserData()).modifiedProperty().addListener( modifiedListener );
110
          } );
111
        } else if( change.wasRemoved() ) {
112
          change.getRemoved().stream().forEach( (tab) -> {
113
            ((FileEditorTab)tab.getUserData()).modifiedProperty().removeListener( modifiedListener );
114
          } );
115
        }
116
      }
117
118
      // Changes in the tabs may also change anyFileEditorModified property
119
      // (e.g. closed modified file)
120
      modifiedListener.changed( null, null, null );
121
    } );
122
  }
123
124
  public <T extends Event, U extends T> void addEventListener(
125
    final EventPattern<? super T, ? extends U> event,
126
    final Consumer<? super U> consumer ) {
127
    getActiveFileEditor().addEventListener( event, consumer );
128
  }
129
130
  /**
131
   * Delegates to the active file editor pane, and, ultimately, to its text
132
   * area.
133
   *
134
   * @param map The map of methods to events.
135
   */
136
  public void addEventListener( final InputMap<InputEvent> map ) {
137
    getActiveFileEditor().addEventListener( map );
138
  }
139
140
  public void removeEventListener( final InputMap<InputEvent> map ) {
141
    getActiveFileEditor().removeEventListener( map );
142
  }
143
144
  @Override
145
  public void changed(
146
    final ObservableValue<? extends Tab> observable,
147
    final Tab oldTab,
148
    final Tab newTab ) {
149
150
    if( newTab != null ) {
151
      this.activeFileEditor.set( (FileEditorTab)newTab.getUserData() );
152
    }
153
  }
154
155
  Node getNode() {
156
    return this;
157
  }
158
159
  /**
160
   * Allows clients to manipulate the editor content directly.
161
   *
162
   * @return The text area for the active file editor.
163
   */
164
  public StyledTextArea getEditor() {
165
    return getActiveFileEditor().getEditorPane().getEditor();
166
  }
167
168
  public FileEditorTab getActiveFileEditor() {
169
    return this.activeFileEditor.get();
170
  }
171
172
  ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() {
173
    return this.activeFileEditor.getReadOnlyProperty();
174
  }
175
176
  ReadOnlyBooleanProperty anyFileEditorModifiedProperty() {
177
    return this.anyFileEditorModified.getReadOnlyProperty();
178
  }
179
180
  private FileEditorTab createFileEditor( final Path path ) {
181
    final FileEditorTab tab = new FileEditorTab( path );
182
183
    tab.setOnCloseRequest( e -> {
184
      if( !canCloseEditor( tab ) ) {
185
        e.consume();
186
      }
187
    } );
188
189
    return tab;
190
  }
191
192
  /**
193
   * Called when the user selects New from the File menu.
194
   *
195
   * @return The newly added tab.
196
   */
197
  FileEditorTab newEditor() {
198
    final FileEditorTab tab = createFileEditor( null );
199
200
    getTabs().add( tab );
201
    getSelectionModel().select( tab );
202
    return tab;
203
  }
204
205
  List<FileEditorTab> openFileDialog() {
206
    final FileChooser dialog
207
      = createFileChooser( get( "Dialog.file.choose.open.title" ) );
208
    final List<File> files = dialog.showOpenMultipleDialog( getWindow() );
209
210
    return (files != null && !files.isEmpty())
211
      ? openFiles( files )
212
      : new ArrayList<>();
213
  }
214
215
  /**
216
   * Opens the files into new editors, unless one of those files was a
217
   * definition file. The definition file is loaded into the definition pane,
218
   * but only the first one selected (multiple definition files will result in a
219
   * warning).
220
   *
221
   * @param files The list of non-definition files that the were requested to
222
   * open.
223
   *
224
   * @return A list of files that can be opened in text editors.
225
   */
226
  private List<FileEditorTab> openFiles( final List<File> files ) {
227
    final List<FileEditorTab> openedEditors = new ArrayList<>();
228
229
    final FileTypePredicate predicate
230
      = new FileTypePredicate( createExtensionFilter( "definition" ).getExtensions() );
231
232
    // The user might have opened muliple definitions files. These will
233
    // be discarded from the text editable files.
234
    final List<File> definitions
235
      = files.stream().filter( predicate ).collect( Collectors.toList() );
236
237
    // Create a modifiable list to remove any definition files that were
238
    // opened.
239
    final List<File> editors = new ArrayList<>( files );
240
    editors.removeAll( definitions );
241
242
    // If there are any editor-friendly files opened (e.g,. Markdown, XML), then
243
    // open them up in new tabs.
244
    if( editors.size() > 0 ) {
245
      saveLastDirectory( editors.get( 0 ) );
246
      openedEditors.addAll( openEditors( editors, 0 ) );
247
    }
248
249
    if( definitions.size() > 0 ) {
250
      openDefinition( definitions.get( 0 ) );
251
    }
252
253
    return openedEditors;
254
  }
255
256
  private List<FileEditorTab> openEditors( final List<File> files, final int activeIndex ) {
257
    final List<FileEditorTab> editors = new ArrayList<>();
258
    final List<Tab> tabs = getTabs();
259
    
260
    // Close single unmodified "Untitled" tab.
261
    if( tabs.size() == 1 ) {
262
      final FileEditorTab fileEditor = (FileEditorTab)(tabs.get( 0 ).getUserData());
263
264
      if( fileEditor.getPath() == null && !fileEditor.isModified() ) {
265
        closeEditor( fileEditor, false );
266
      }
267
    }
268
269
    for( int i = 0; i < files.size(); i++ ) {
270
      Path path = files.get( i ).toPath();
271
272
      // Check whether file is already opened.
273
      FileEditorTab fileEditor = findEditor( path );
274
275
      if( fileEditor == null ) {
276
        fileEditor = createFileEditor( path );
277
        getTabs().add( fileEditor );
278
        editors.add( fileEditor );
279
      }
280
281
      // Select first file.
282
      if( i == activeIndex ) {
283
        getSelectionModel().select( fileEditor );
284
      }
285
    }
286
287
    return editors;
288
  }
289
290
  /**
291
   * Called when the user has opened a definition file (using the file open
292
   * dialog box). This will replace the current set of definitions for the
293
   * active tab.
294
   *
295
   * @param definition The file to open.
296
   */
297
  private void openDefinition( final File definition ) {
298
    System.out.println( "open definition file: " + definition.toString() );
299
  }
300
301
  boolean saveEditor( final FileEditorTab fileEditor ) {
302
    if( fileEditor == null || !fileEditor.isModified() ) {
303
      return true;
304
    }
305
306
    if( fileEditor.getPath() == null ) {
307
      getSelectionModel().select( fileEditor );
308
309
      final FileChooser fileChooser = createFileChooser( Messages.get( "Dialog.file.choose.save.title" ) );
310
      final File file = fileChooser.showSaveDialog( getWindow() );
311
      if( file == null ) {
312
        return false;
313
      }
314
315
      saveLastDirectory( file );
316
      fileEditor.setPath( file.toPath() );
317
    }
318
319
    return fileEditor.save();
320
  }
321
322
  boolean saveAllEditors() {
323
    final FileEditorTab[] allEditors = getAllEditors();
324
325
    boolean success = true;
326
    for( FileEditorTab fileEditor : allEditors ) {
327
      if( !saveEditor( fileEditor ) ) {
328
        success = false;
329
      }
330
    }
331
332
    return success;
333
  }
334
335
  boolean canCloseEditor( final FileEditorTab tab ) {
336
    if( !tab.isModified() ) {
337
      return true;
338
    }
339
340
    final AlertMessage message = getAlertService().createAlertMessage(
341
      Messages.get( "Alert.file.close.title" ),
342
      Messages.get( "Alert.file.close.text" ),
343
      tab.getText()
344
    );
345
346
    final Alert alert = getAlertService().createAlertConfirmation( message );
347
    final ButtonType response = alert.showAndWait().get();
348
349
    return response == YES ? saveEditor( tab ) : response == NO;
350
  }
351
352
  private AlertService getAlertService() {
353
    return this.alertService;
354
  }
355
356
  boolean closeEditor( FileEditorTab fileEditor, boolean save ) {
357
    if( fileEditor == null ) {
358
      return true;
359
    }
360
361
    final Tab tab = fileEditor;
362
363
    if( save ) {
364
      Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT );
365
      Event.fireEvent( tab, event );
366
      if( event.isConsumed() ) {
367
        return false;
368
      }
369
    }
370
371
    getTabs().remove( tab );
372
    if( tab.getOnClosed() != null ) {
373
      Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) );
374
    }
375
376
    return true;
377
  }
378
379
  boolean closeAllEditors() {
380
    FileEditorTab[] allEditors = getAllEditors();
381
    FileEditorTab activeEditor = activeFileEditor.get();
382
383
    // try to save active tab first because in case the user decides to cancel,
384
    // then it stays active
385
    if( activeEditor != null && !canCloseEditor( activeEditor ) ) {
386
      return false;
387
    }
388
389
    // save modified tabs
390
    for( int i = 0; i < allEditors.length; i++ ) {
391
      FileEditorTab fileEditor = allEditors[ i ];
392
      if( fileEditor == activeEditor ) {
393
        continue;
394
      }
395
396
      if( fileEditor.isModified() ) {
397
        // activate the modified tab to make its modified content visible to the user
398
        getSelectionModel().select( i );
399
400
        if( !canCloseEditor( fileEditor ) ) {
401
          return false;
402
        }
403
      }
404
    }
405
406
    // Close all tabs.
407
    for( final FileEditorTab fileEditor : allEditors ) {
408
      if( !closeEditor( fileEditor, false ) ) {
409
        return false;
410
      }
411
    }
412
413
    saveState( allEditors, activeEditor );
414
415
    return getTabs().isEmpty();
416
  }
417
418
  private FileEditorTab[] getAllEditors() {
419
    final ObservableList<Tab> tabs = getTabs();
420
    final FileEditorTab[] allEditors = new FileEditorTab[ tabs.size() ];
421
    final int length = tabs.size();
422
423
    for( int i = 0; i < length; i++ ) {
424
      allEditors[ i ] = (FileEditorTab)tabs.get( i ).getUserData();
425
    }
426
427
    return allEditors;
428
  }
429
430
  private FileEditorTab findEditor( Path path ) {
431
    for( final Tab tab : getTabs() ) {
432
      final FileEditorTab fileEditor = (FileEditorTab)tab.getUserData();
433
434
      if( path.equals( fileEditor.getPath() ) ) {
435
        return fileEditor;
436
      }
437
    }
438
439
    return null;
440
  }
441
442
  private Settings getSettings() {
443
    return this.settings;
444
  }
445
446
  private FileChooser createFileChooser( String title ) {
447
    final FileChooser fileChooser = new FileChooser();
448
449
    fileChooser.setTitle( title );
450
    fileChooser.getExtensionFilters().addAll(
451
      createExtensionFilters() );
452
453
    final String lastDirectory = getState().get( "lastDirectory", null );
454
    File file = new File( (lastDirectory != null) ? lastDirectory : "." );
455
456
    if( !file.isDirectory() ) {
457
      file = new File( "." );
458
    }
459
460
    fileChooser.setInitialDirectory( file );
461
    return fileChooser;
462
  }
463
464
  private List<ExtensionFilter> createExtensionFilters() {
465
    final List<ExtensionFilter> list = new ArrayList<>();
466
467
    // TODO: Return a list of all properties that match the filter prefix.
468
    // This will allow dynamic filters to be added and removed just by
469
    // updating the properties file.
470
    list.add( createExtensionFilter( "markdown" ) );
471
    list.add( createExtensionFilter( "definition" ) );
472
    list.add( createExtensionFilter( "xml" ) );
473
    list.add( createExtensionFilter( "all" ) );
474
    return list;
475
  }
476
477
  private ExtensionFilter createExtensionFilter( final String filetype ) {
478
    final String tKey = String.format( "%s.title.%s", FILTER_PREFIX, filetype );
479
    final String eKey = String.format( "%s.ext.%s", FILTER_PREFIX, filetype );
480
481
    return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) );
482
  }
483
484
  private List<String> getExtensions( final String key ) {
485
    return getStringSettingList( key );
486
  }
487
488
  private List<String> getStringSettingList( String key ) {
489
    return getStringSettingList( key, null );
490
  }
491
492
  private List<String> getStringSettingList( String key, List<String> values ) {
493
    return getSettings().getStringSettingList( key, values );
494
  }
495
496
  private void saveLastDirectory( File file ) {
497
    getState().put( "lastDirectory", file.getParent() );
498
  }
499
500
  public void restoreState() {
501
    int activeIndex = 0;
502
503
    final Preferences state = getState();
504
    final String[] fileNames = Utils.getPrefsStrings( state, "file" );
505
    final String activeFileName = state.get( "activeFile", null );
506
507
    final ArrayList<File> files = new ArrayList<>( fileNames.length );
508
509
    for( final String fileName : fileNames ) {
510
      final File file = new File( fileName );
511
512
      if( file.exists() ) {
513
        files.add( file );
514
515
        if( fileName.equals( activeFileName ) ) {
516
          activeIndex = files.size() - 1;
517
        }
518
      }
519
    }
520
521
    if( files.isEmpty() ) {
522
      newEditor();
523
      return;
524
    }
525
526
    openEditors( files, activeIndex );
527
  }
528
529
  private void saveState( final FileEditorTab[] allEditors, final FileEditorTab activeEditor ) {
530
    final List<String> fileNames = new ArrayList<>( allEditors.length );
531
532
    for( final FileEditorTab fileEditor : allEditors ) {
533
      if( fileEditor.getPath() != null ) {
534
        fileNames.add( fileEditor.getPath().toString() );
535
      }
536
    }
537
538
    final Preferences state = getState();
539
    Utils.putPrefsStrings( state, "file", fileNames.toArray( new String[ fileNames.size() ] ) );
540
541
    if( activeEditor != null && activeEditor.getPath() != null ) {
542
      state.put( "activeFile", activeEditor.getPath().toString() );
543
    } else {
544
      state.remove( "activeFile" );
545
    }
546
  }
547
548
  private Window getWindow() {
549
    return getScene().getWindow();
550
  }
551
552
  protected Options getOptions() {
553
    return this.options;
554
  }
555
556
  protected Preferences getState() {
557
    return getOptions().getState();
558
  }
559
560
}
1561
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.LOGO_128;
31
import static com.scrivenvar.Constants.LOGO_16;
32
import static com.scrivenvar.Constants.LOGO_256;
33
import static com.scrivenvar.Constants.LOGO_32;
34
import static com.scrivenvar.Constants.LOGO_512;
35
import com.scrivenvar.service.Options;
36
import com.scrivenvar.service.events.AlertService;
37
import com.scrivenvar.util.StageState;
38
import javafx.application.Application;
39
import javafx.scene.Scene;
40
import javafx.scene.image.Image;
41
import javafx.stage.Stage;
42
43
/**
44
 * Main application entry point. The application allows users to edit Markdown
45
 * files and see a real-time preview of the edits.
46
 *
47
 * @author Karl Tauber and White Magic Software, Ltd.
48
 */
49
public final class Main extends Application {
50
51
  private static Application app;
52
53
  private final MainWindow mainWindow = new MainWindow();
54
  private final Options options = Services.load( Options.class );
55
56
  public static void main( String[] args ) {
57
    launch( args );
58
  }
59
60
  /**
61
   * Application entry point.
62
   *
63
   * @param stage The primary application stage.
64
   *
65
   * @throws Exception Could not read configuration file.
66
   */
67
  @Override
68
  public void start( final Stage stage ) throws Exception {
69
    initApplication();
70
    initState( stage );
71
    initStage( stage );
72
    initAlertService();
73
    
74
    stage.show();
75
  }
76
77
  private void initApplication() {
78
    app = this;
79
  }
80
81
  private Options getOptions() {
82
    return this.options;
83
  }
84
85
  private String getApplicationTitle() {
86
    return Messages.get( "Main.title" );
87
  }
88
89
  private StageState initState( Stage stage ) {
90
    return new StageState( stage, getOptions().getState() );
91
  }
92
93
  private void initStage( Stage stage ) {
94
    stage.getIcons().addAll(
95
      new Image( LOGO_16 ),
96
      new Image( LOGO_32 ),
97
      new Image( LOGO_128 ),
98
      new Image( LOGO_256 ),
99
      new Image( LOGO_512 ) );
100
    stage.setTitle( getApplicationTitle() );
101
    stage.setScene( getScene() );
102
  }
103
104
  private void initAlertService() {
105
    final AlertService service = Services.load( AlertService.class );
106
    service.setWindow( getScene().getWindow() );
107
  }
108
109
  private Scene getScene() {
110
    return getMainWindow().getScene();
111
  }
112
113
  private MainWindow getMainWindow() {
114
    return this.mainWindow;
115
  }
116
117
  private static Application getApplication() {
118
    return app;
119
  }
120
121
  public static void showDocument( String uri ) {
122
    getApplication().getHostServices().showDocument( uri );
123
  }
124
}
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.LOGO_32;
31
import static com.scrivenvar.Messages.get;
32
import com.scrivenvar.definition.DefinitionPane;
33
import com.scrivenvar.editor.EditorPane;
34
import com.scrivenvar.editor.MarkdownEditorPane;
35
import com.scrivenvar.editor.VariableNameInjector;
36
import com.scrivenvar.options.OptionsDialog;
37
import com.scrivenvar.preview.HTMLPreviewPane;
38
import com.scrivenvar.processors.HTMLPreviewProcessor;
39
import com.scrivenvar.processors.MarkdownCaretInsertionProcessor;
40
import com.scrivenvar.processors.MarkdownCaretReplacementProcessor;
41
import com.scrivenvar.processors.MarkdownProcessor;
42
import com.scrivenvar.processors.Processor;
43
import com.scrivenvar.processors.TextChangeProcessor;
44
import com.scrivenvar.processors.VariableProcessor;
45
import com.scrivenvar.service.Options;
46
import com.scrivenvar.util.Action;
47
import com.scrivenvar.util.ActionUtils;
48
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_DEFINITION;
49
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_EDITOR;
50
import com.scrivenvar.yaml.YamlParser;
51
import com.scrivenvar.yaml.YamlTreeAdapter;
52
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.BOLD;
53
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.CODE;
54
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_ALT;
55
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_CODE_ALT;
56
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FLOPPY_ALT;
57
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FOLDER_OPEN_ALT;
58
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.HEADER;
59
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.ITALIC;
60
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LINK;
61
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_OL;
62
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_UL;
63
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.PICTURE_ALT;
64
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.QUOTE_LEFT;
65
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.REPEAT;
66
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.STRIKETHROUGH;
67
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.UNDO;
68
import java.io.IOException;
69
import java.io.InputStream;
70
import java.util.Map;
71
import java.util.function.Function;
72
import java.util.prefs.Preferences;
73
import javafx.beans.binding.Bindings;
74
import javafx.beans.binding.BooleanBinding;
75
import javafx.beans.property.BooleanProperty;
76
import javafx.beans.property.SimpleBooleanProperty;
77
import javafx.beans.value.ObservableBooleanValue;
78
import javafx.beans.value.ObservableValue;
79
import javafx.collections.ListChangeListener.Change;
80
import javafx.collections.ObservableList;
81
import javafx.event.Event;
82
import javafx.scene.Node;
83
import javafx.scene.Scene;
84
import javafx.scene.control.Alert;
85
import javafx.scene.control.Alert.AlertType;
86
import javafx.scene.control.Menu;
87
import javafx.scene.control.MenuBar;
88
import javafx.scene.control.SplitPane;
89
import javafx.scene.control.Tab;
90
import javafx.scene.control.ToolBar;
91
import javafx.scene.control.TreeView;
92
import javafx.scene.image.Image;
93
import javafx.scene.image.ImageView;
94
import static javafx.scene.input.KeyCode.ESCAPE;
95
import javafx.scene.input.KeyEvent;
96
import static javafx.scene.input.KeyEvent.CHAR_UNDEFINED;
97
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
98
import javafx.scene.layout.BorderPane;
99
import javafx.scene.layout.VBox;
100
import javafx.stage.Window;
101
import javafx.stage.WindowEvent;
102
import org.fxmisc.richtext.StyleClassedTextArea;
103
104
/**
105
 * Main window containing a tab pane in the center for file editors.
106
 *
107
 * @author Karl Tauber and White Magic Software, Ltd.
108
 */
109
public class MainWindow {
110
111
  private final Options options = Services.load( Options.class );
112
113
  private Scene scene;
114
115
  private TreeView<String> treeView;
116
  private FileEditorTabPane fileEditorPane;
117
  private DefinitionPane definitionPane;
118
119
  private VariableNameInjector variableNameInjector;
120
121
  private YamlTreeAdapter yamlTreeAdapter;
122
  private YamlParser yamlParser;
123
124
  private MenuBar menuBar;
125
126
  public MainWindow() {
127
    initLayout();
128
    initVariableNameInjector();
129
  }
130
131
  private void initLayout() {
132
    final SplitPane splitPane = new SplitPane(
133
      getDefinitionPane().getNode(),
134
      getFileEditorPane().getNode() );
135
136
    splitPane.setDividerPositions(
137
      getFloat( K_PANE_SPLIT_DEFINITION, .05f ),
138
      getFloat( K_PANE_SPLIT_EDITOR, .95f ) );
139
140
    // See: http://broadlyapplicable.blogspot.ca/2015/03/javafx-capture-restore-splitpane.html
141
    final BorderPane borderPane = new BorderPane();
142
    borderPane.setPrefSize( 1024, 800 );
143
    borderPane.setTop( createMenuBar() );
144
    borderPane.setCenter( splitPane );
145
    
146
    final Scene appScene = new Scene( borderPane );
147
    setScene( appScene );
148
    appScene.getStylesheets().add( Constants.STYLESHEET_PREVIEW );
149
    appScene.windowProperty().addListener(
150
      (observable, oldWindow, newWindow) -> {
151
        newWindow.setOnCloseRequest( e -> {
152
          if( !getFileEditorPane().closeAllEditors() ) {
153
            e.consume();
154
          }
155
        } );
156
157
        // Workaround JavaFX bug: deselect menubar if window loses focus.
158
        newWindow.focusedProperty().addListener(
159
          (obs, oldFocused, newFocused) -> {
160
            if( !newFocused ) {
161
              // Send an ESC key event to the menubar
162
              this.menuBar.fireEvent(
163
                new KeyEvent(
164
                  KEY_PRESSED, CHAR_UNDEFINED, "", ESCAPE,
165
                  false, false, false, false ) );
166
            }
167
          } );
168
      } );
169
  }
170
171
  private void initVariableNameInjector() {
172
    setVariableNameInjector( new VariableNameInjector(
173
      getFileEditorPane(),
174
      getDefinitionPane() )
175
    );
176
  }
177
178
  private Window getWindow() {
179
    return getScene().getWindow();
180
  }
181
182
  public Scene getScene() {
183
    return this.scene;
184
  }
185
186
  private void setScene( Scene scene ) {
187
    this.scene = scene;
188
  }
189
190
  /**
191
   * Creates a boolean property that is bound to another boolean value of the
192
   * active editor.
193
   */
194
  private BooleanProperty createActiveBooleanProperty(
195
    final Function<FileEditorTab, ObservableBooleanValue> func ) {
196
197
    final BooleanProperty b = new SimpleBooleanProperty();
198
    final FileEditorTab tab = getActiveFileEditor();
199
200
    if( tab != null ) {
201
      b.bind( func.apply( tab ) );
202
    }
203
204
    getFileEditorPane().activeFileEditorProperty().addListener(
205
      (observable, oldFileEditor, newFileEditor) -> {
206
        b.unbind();
207
208
        if( newFileEditor != null ) {
209
          b.bind( func.apply( newFileEditor ) );
210
        } else {
211
          b.set( false );
212
        }
213
      } );
214
215
    return b;
216
  }
217
218
  //---- File actions -------------------------------------------------------
219
  private void fileNew() {
220
    getFileEditorPane().newEditor();
221
  }
222
223
  private void fileOpen() {
224
    getFileEditorPane().openFileDialog();
225
  }
226
227
  private void fileClose() {
228
    getFileEditorPane().closeEditor( getActiveFileEditor(), true );
229
  }
230
231
  private void fileCloseAll() {
232
    getFileEditorPane().closeAllEditors();
233
  }
234
235
  private void fileSave() {
236
    getFileEditorPane().saveEditor( getActiveFileEditor() );
237
  }
238
239
  private void fileSaveAll() {
240
    getFileEditorPane().saveAllEditors();
241
  }
242
243
  private void fileExit() {
244
    final Window window = getWindow();
245
    Event.fireEvent( window,
246
      new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) );
247
  }
248
249
  //---- Tools actions ------------------------------------------------------
250
  private void toolsOptions() {
251
    new OptionsDialog( getWindow() ).showAndWait();
252
  }
253
254
  //---- Help actions -------------------------------------------------------
255
  private void helpAbout() {
256
    Alert alert = new Alert( AlertType.INFORMATION );
257
    alert.setTitle( Messages.get( "Dialog.about.title" ) );
258
    alert.setHeaderText( Messages.get( "Dialog.about.header" ) );
259
    alert.setContentText( Messages.get( "Dialog.about.content" ) );
260
    alert.setGraphic( new ImageView( new Image( LOGO_32 ) ) );
261
    alert.initOwner( getWindow() );
262
263
    alert.showAndWait();
264
  }
265
266
  private FileEditorTabPane getFileEditorPane() {
267
    if( this.fileEditorPane == null ) {
268
      this.fileEditorPane = createFileEditorPane();
269
    }
270
271
    return this.fileEditorPane;
272
  }
273
274
  private FileEditorTabPane createFileEditorPane() {
275
    // Create an editor pane to hold file editor tabs.
276
    final FileEditorTabPane editorPane = new FileEditorTabPane();
277
278
    // Make sure the text processor kicks off when new files are opened.
279
    final ObservableList<Tab> tabs = editorPane.getTabs();
280
281
    tabs.addListener( (Change<? extends Tab> change) -> {
282
      while( change.next() ) {
283
        if( change.wasAdded() ) {
284
          // Multiple tabs can be added simultaneously.
285
          for( final Tab tab : change.getAddedSubList() ) {
286
            addListener( (FileEditorTab)tab );
287
          }
288
        }
289
      }
290
    } );
291
292
    // After the processors are in place, restore the previously closed
293
    // tabs. Adding them will trigger the change event, above.
294
    editorPane.restoreState();
295
296
    return editorPane;
297
  }
298
299
  private MarkdownEditorPane getActiveEditor() {
300
    return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane());
301
  }
302
303
  private FileEditorTab getActiveFileEditor() {
304
    return getFileEditorPane().getActiveFileEditor();
305
  }
306
307
  /**
308
   * Listens for changes to tabs and their text editors.
309
   *
310
   * @see https://github.com/DaveJarvis/scrivenvar/issues/17
311
   * @see https://github.com/DaveJarvis/scrivenvar/issues/18
312
   *
313
   * @param tab The file editor tab that contains a text editor.
314
   */
315
  private void addListener( FileEditorTab tab ) {
316
    final HTMLPreviewPane previewPane = tab.getPreviewPane();
317
    final EditorPane editorPanel = tab.getEditorPane();
318
    final StyleClassedTextArea editor = editorPanel.getEditor();
319
320
    // TODO: Use a factory based on the filename extension. The default
321
    // extension will be for a markdown file (e.g., on file new).
322
    final Processor<String> hpp = new HTMLPreviewProcessor( previewPane );
323
    final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp );
324
    final Processor<String> mp = new MarkdownProcessor( mcrp );
325
    final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, editor );
326
    final Processor<String> vnp = new VariableProcessor( mcip, getResolvedMap() );
327
    final TextChangeProcessor tp = new TextChangeProcessor( vnp );
328
329
    editorPanel.addChangeListener( tp );
330
    editorPanel.addCaretParagraphListener(
331
      (final ObservableValue<? extends Integer> observable,
332
        final Integer oldValue, final Integer newValue) -> {
333
        
334
        // Kick off the processing chain at the variable processor when the
335
        // cursor changes paragraphs. This might cause some slight duplication
336
        // when the Enter key is pressed.
337
        vnp.processChain( editor.getText() );
338
      } );
339
  }
340
341
  protected DefinitionPane createDefinitionPane() {
342
    return new DefinitionPane( getTreeView() );
343
  }
344
345
  private DefinitionPane getDefinitionPane() {
346
    if( this.definitionPane == null ) {
347
      this.definitionPane = createDefinitionPane();
348
    }
349
350
    return this.definitionPane;
351
  }
352
353
  public MenuBar getMenuBar() {
354
    return menuBar;
355
  }
356
357
  public void setMenuBar( MenuBar menuBar ) {
358
    this.menuBar = menuBar;
359
  }
360
361
  public VariableNameInjector getVariableNameInjector() {
362
    return this.variableNameInjector;
363
  }
364
365
  public void setVariableNameInjector( VariableNameInjector variableNameInjector ) {
366
    this.variableNameInjector = variableNameInjector;
367
  }
368
369
  private float getFloat( final String key, final float defaultValue ) {
370
    return getPreferences().getFloat( key, defaultValue );
371
  }
372
373
  private Preferences getPreferences() {
374
    return getOptions().getState();
375
  }
376
377
  private Options getOptions() {
378
    return this.options;
379
  }
380
381
  private synchronized TreeView<String> getTreeView() throws RuntimeException {
382
    if( this.treeView == null ) {
383
      try {
384
        this.treeView = createTreeView();
385
      } catch( IOException ex ) {
386
387
        // TODO: Pop an error message.
388
        throw new RuntimeException( ex );
389
      }
390
    }
391
392
    return this.treeView;
393
  }
394
395
  private InputStream asStream( final String resource ) {
396
    return getClass().getResourceAsStream( resource );
397
  }
398
399
  private TreeView<String> createTreeView() throws IOException {
400
    // TODO: Associate variable file with path to current file.
401
    return getYamlTreeAdapter().adapt(
402
      asStream( "/com/scrivenvar/variables.yaml" ),
403
      get( "Pane.defintion.node.root.title" )
404
    );
405
  }
406
407
  private Map<String, String> getResolvedMap() {
408
    return getYamlParser().createResolvedMap();
409
  }
410
411
  private YamlTreeAdapter getYamlTreeAdapter() {
412
    if( this.yamlTreeAdapter == null ) {
413
      setYamlTreeAdapter( new YamlTreeAdapter( getYamlParser() ) );
414
    }
415
416
    return this.yamlTreeAdapter;
417
  }
418
419
  private void setYamlTreeAdapter( final YamlTreeAdapter yamlTreeAdapter ) {
420
    this.yamlTreeAdapter = yamlTreeAdapter;
421
  }
422
423
  private YamlParser getYamlParser() {
424
    if( this.yamlParser == null ) {
425
      setYamlParser( new YamlParser() );
426
    }
427
428
    return this.yamlParser;
429
  }
430
431
  private void setYamlParser( final YamlParser yamlParser ) {
432
    this.yamlParser = yamlParser;
433
  }
434
435
  private Node createMenuBar() {
436
    final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull();
437
438
    // File actions
439
    Action fileNewAction = new Action( Messages.get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() );
440
    Action fileOpenAction = new Action( Messages.get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() );
441
    Action fileCloseAction = new Action( Messages.get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull );
442
    Action fileCloseAllAction = new Action( Messages.get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull );
443
    Action fileSaveAction = new Action( Messages.get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
444
      createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() );
445
    Action fileSaveAllAction = new Action( Messages.get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(),
446
      Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
447
    Action fileExitAction = new Action( Messages.get( "Main.menu.file.exit" ), null, null, e -> fileExit() );
448
449
    // Edit actions
450
    Action editUndoAction = new Action( Messages.get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO,
451
      e -> getActiveEditor().undo(),
452
      createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() );
453
    Action editRedoAction = new Action( Messages.get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT,
454
      e -> getActiveEditor().redo(),
455
      createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() );
456
457
    // Insert actions
458
    Action insertBoldAction = new Action( Messages.get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD,
459
      e -> getActiveEditor().surroundSelection( "**", "**" ),
460
      activeFileEditorIsNull );
461
    Action insertItalicAction = new Action( Messages.get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
462
      e -> getActiveEditor().surroundSelection( "*", "*" ),
463
      activeFileEditorIsNull );
464
    Action insertStrikethroughAction = new Action( Messages.get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
465
      e -> getActiveEditor().surroundSelection( "~~", "~~" ),
466
      activeFileEditorIsNull );
467
    Action insertBlockquoteAction = new Action( Messages.get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
468
      e -> getActiveEditor().surroundSelection( "\n\n> ", "" ),
469
      activeFileEditorIsNull );
470
    Action insertCodeAction = new Action( Messages.get( "Main.menu.insert.code" ), "Shortcut+K", CODE,
471
      e -> getActiveEditor().surroundSelection( "`", "`" ),
472
      activeFileEditorIsNull );
473
    Action insertFencedCodeBlockAction = new Action( Messages.get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT,
474
      e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", Messages.get( "Main.menu.insert.fenced_code_block.prompt" ) ),
475
      activeFileEditorIsNull );
476
477
    Action insertLinkAction = new Action( Messages.get( "Main.menu.insert.link" ), "Shortcut+L", LINK,
478
      e -> getActiveEditor().insertLink(),
479
      activeFileEditorIsNull );
480
    Action insertImageAction = new Action( Messages.get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT,
481
      e -> getActiveEditor().insertImage(),
482
      activeFileEditorIsNull );
483
484
    final Action[] headers = new Action[ 6 ];
485
486
    // Insert header actions (H1 ... H6)
487
    for( int i = 1; i <= 6; i++ ) {
488
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
489
      final String markup = String.format( "\n\n%s ", hashes );
490
      final String text = Messages.get( "Main.menu.insert.header_" + i );
491
      final String accelerator = "Shortcut+" + i;
492
      final String prompt = Messages.get( "Main.menu.insert.header_" + i + ".prompt" );
493
494
      headers[ i - 1 ] = new Action( text, accelerator, HEADER,
495
        e -> getActiveEditor().surroundSelection( markup, "", prompt ),
496
        activeFileEditorIsNull );
497
    }
498
499
    Action insertUnorderedListAction = new Action( Messages.get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
500
      e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
501
      activeFileEditorIsNull );
502
    Action insertOrderedListAction = new Action( Messages.get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
503
      e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
504
      activeFileEditorIsNull );
505
    Action insertHorizontalRuleAction = new Action( Messages.get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
506
      e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
507
      activeFileEditorIsNull );
508
509
    // Tools actions
510
    Action toolsOptionsAction = new Action( Messages.get( "Main.menu.tools.options" ), "Shortcut+,", null, e -> toolsOptions() );
511
512
    // Help actions
513
    Action helpAboutAction = new Action( Messages.get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
514
515
    //---- MenuBar ----
516
    Menu fileMenu = ActionUtils.createMenu( Messages.get( "Main.menu.file" ),
517
      fileNewAction,
518
      fileOpenAction,
519
      null,
520
      fileCloseAction,
521
      fileCloseAllAction,
522
      null,
523
      fileSaveAction,
524
      fileSaveAllAction,
525
      null,
526
      fileExitAction );
527
528
    Menu editMenu = ActionUtils.createMenu( Messages.get( "Main.menu.edit" ),
529
      editUndoAction,
530
      editRedoAction );
531
532
    Menu insertMenu = ActionUtils.createMenu( Messages.get( "Main.menu.insert" ),
533
      insertBoldAction,
534
      insertItalicAction,
535
      insertStrikethroughAction,
536
      insertBlockquoteAction,
537
      insertCodeAction,
538
      insertFencedCodeBlockAction,
539
      null,
540
      insertLinkAction,
541
      insertImageAction,
542
      null,
543
      headers[ 0 ],
544
      headers[ 1 ],
545
      headers[ 2 ],
546
      headers[ 3 ],
547
      headers[ 4 ],
548
      headers[ 5 ],
549
      null,
550
      insertUnorderedListAction,
551
      insertOrderedListAction,
552
      insertHorizontalRuleAction );
553
554
    Menu toolsMenu = ActionUtils.createMenu( Messages.get( "Main.menu.tools" ),
555
      toolsOptionsAction );
556
557
    Menu helpMenu = ActionUtils.createMenu( Messages.get( "Main.menu.help" ),
558
      helpAboutAction );
559
560
    menuBar = new MenuBar( fileMenu, editMenu, insertMenu, toolsMenu, helpMenu );
561
562
    //---- ToolBar ----
563
    ToolBar toolBar = ActionUtils.createToolBar(
564
      fileNewAction,
565
      fileOpenAction,
566
      fileSaveAction,
567
      null,
568
      editUndoAction,
569
      editRedoAction,
570
      null,
571
      insertBoldAction,
572
      insertItalicAction,
573
      insertBlockquoteAction,
574
      insertCodeAction,
575
      insertFencedCodeBlockAction,
576
      null,
577
      insertLinkAction,
578
      insertImageAction,
579
      null,
580
      headers[ 0 ],
581
      null,
582
      insertUnorderedListAction,
583
      insertOrderedListAction );
584
585
    return new VBox( menuBar, toolBar );
586
  }
587
}
1588
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 static com.scrivenvar.Constants.BUNDLE_NAME;
30
import java.text.MessageFormat;
31
import java.util.ResourceBundle;
32
import java.util.Stack;
33
34
/**
35
 * Recursively resolves message properties. Property values can refer to other
36
 * properties using a <code>${var}</code> syntax.
37
 *
38
 * @author Karl Tauber, Dave Jarvis
39
 */
40
public class Messages {
41
42
  private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle( BUNDLE_NAME );
43
44
  private Messages() {
45
  }
46
47
  /**
48
   * Return the value of a resource bundle value after having resolved any
49
   * references to other bundle variables.
50
   *
51
   * @param props The bundle containing resolvable properties.
52
   * @param s The value for a key to resolve.
53
   *
54
   * @return The value of the key with all references recursively dereferenced.
55
   */
56
  private static String resolve( ResourceBundle props, String s ) {
57
    final int len = s.length();
58
    final Stack<StringBuilder> stack = new Stack<>();
59
60
    StringBuilder sb = new StringBuilder( 256 );
61
    boolean open = false;
62
63
    for( int i = 0; i < len; i++ ) {
64
      final char c = s.charAt( i );
65
66
      switch( c ) {
67
        case '$': {
68
          if( i + 1 < len && s.charAt( i + 1 ) == '{' ) {
69
            stack.push( sb );
70
            sb = new StringBuilder( 256 );
71
            i++;
72
            open = true;
73
          }
74
75
          break;
76
        }
77
78
        case '}': {
79
          if( open ) {
80
            open = false;
81
            final String name = sb.toString();
82
83
            sb = stack.pop();
84
            sb.append( props.getString( name ) );
85
            break;
86
          }
87
        }
88
89
        default: {
90
          sb.append( c );
91
          break;
92
        }
93
      }
94
    }
95
96
    if( open ) {
97
      throw new IllegalArgumentException( "missing '}'" );
98
    }
99
100
    return sb.toString();
101
  }
102
103
  /**
104
   * Returns the value for a key from the message bundle.
105
   *
106
   * @param key Retrieve the value for this key.
107
   *
108
   * @return The value for the key.
109
   */
110
  public static String get( String key ) {
111
    String result;
112
113
    try {
114
      result = resolve( RESOURCE_BUNDLE, RESOURCE_BUNDLE.getString( key ) );
115
    } catch( Exception e ) {
116
      
117
      // Instead of crashing, launch the application and show the resource
118
      // name.
119
      result = key;
120
    }
121
122
    return result;
123
  }
124
125
  /**
126
   * Returns the value for a key from the message bundle with the arguments
127
   * replacing <code>{#}</code> place holders.
128
   *
129
   * @param key Retrieve the value for this key.
130
   * @param args The values to substitute for place holders.
131
   *
132
   * @return The value for the key.
133
   */
134
  public static String get( String key, Object... args ) {
135
    return MessageFormat.format( get( key ), args );
136
  }
137
}
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/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;
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/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;
29
30
import static com.scrivenvar.Messages.get;
31
import com.scrivenvar.definition.DefinitionPane;
32
import com.scrivenvar.yaml.YamlParser;
33
import com.scrivenvar.yaml.YamlTreeAdapter;
34
import java.io.IOException;
35
import java.io.InputStream;
36
import javafx.application.Application;
37
import javafx.scene.Scene;
38
import javafx.scene.control.TreeView;
39
import javafx.scene.layout.BorderPane;
40
import javafx.stage.Stage;
41
import org.fxmisc.flowless.VirtualizedScrollPane;
42
import org.fxmisc.richtext.StyleClassedTextArea;
43
44
/**
45
 * TestDefinitionPane application for debugging and head-banging.
46
 */
47
public abstract class TestHarness extends Application {
48
49
  private static Application app;
50
  private Scene scene;
51
52
  /**
53
   * Application entry point.
54
   *
55
   * @param stage The primary application stage.
56
   *
57
   * @throws Exception Could not read configuration file.
58
   */
59
  @Override
60
  public void start( final Stage stage ) throws Exception {
61
    initApplication();
62
    initScene();
63
    initStage( stage );
64
  }
65
  
66
  protected TreeView<String> createTreeView() throws IOException {
67
    return new YamlTreeAdapter( new YamlParser() ).adapt(
68
      asStream( "/com/scrivenvar/variables.yaml" ),
69
      get( "Pane.defintion.node.root.title" )
70
    );
71
  }
72
  
73
  protected DefinitionPane createDefinitionPane( TreeView<String> root ) {
74
    return new DefinitionPane( root );
75
  }
76
77
  private void initApplication() {
78
    app = this;
79
  }
80
81
  private void initScene() {
82
    final StyleClassedTextArea editor = new StyleClassedTextArea( false );
83
    final VirtualizedScrollPane<StyleClassedTextArea> scrollPane = new VirtualizedScrollPane<>( editor );
84
85
    final BorderPane borderPane = new BorderPane();
86
    borderPane.setPrefSize( 1024, 800 );
87
    borderPane.setCenter( scrollPane );
88
89
    setScene( new Scene( borderPane ) );
90
  }
91
92
  private void initStage( Stage stage ) {
93
    stage.setScene( getScene() );
94
  }
95
96
  private Scene getScene() {
97
    return this.scene;
98
  }
99
100
  private void setScene( Scene scene ) {
101
    this.scene = scene;
102
  }
103
104
  private static Application getApplication() {
105
    return app;
106
  }
107
108
  public static void showDocument( String uri ) {
109
    getApplication().getHostServices().showDocument( uri );
110
  }
111
112
  protected InputStream asStream( String resource ) {
113
    return getClass().getResourceAsStream( resource );
114
  }
115
}
1116
A src/main/java/com/scrivenvar/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;
29
30
import com.scrivenvar.ui.VariableTreeItem;
31
import java.util.Collection;
32
import java.util.HashMap;
33
import java.util.Map;
34
import static java.util.concurrent.ThreadLocalRandom.current;
35
import java.util.concurrent.TimeUnit;
36
import static java.util.concurrent.TimeUnit.DAYS;
37
import static java.util.concurrent.TimeUnit.HOURS;
38
import static java.util.concurrent.TimeUnit.MILLISECONDS;
39
import static java.util.concurrent.TimeUnit.MINUTES;
40
import static java.util.concurrent.TimeUnit.NANOSECONDS;
41
import static java.util.concurrent.TimeUnit.SECONDS;
42
import static javafx.application.Application.launch;
43
import javafx.scene.control.TreeItem;
44
import javafx.scene.control.TreeView;
45
import javafx.stage.Stage;
46
import org.ahocorasick.trie.*;
47
import org.ahocorasick.trie.Trie.TrieBuilder;
48
import static org.apache.commons.lang.RandomStringUtils.randomNumeric;
49
import org.apache.commons.lang.StringUtils;
50
51
/**
52
 * Tests substituting variable definitions with their values in a swath of text.
53
 *
54
 * @author White Magic Software, Ltd.
55
 */
56
public class TestVariableNameProcessor extends TestHarness {
57
58
  private final static int TEXT_SIZE = 1000000;
59
  private final static int MATCHES_DIVISOR = 1000;
60
61
  private final static StringBuilder SOURCE
62
    = new StringBuilder( randomNumeric( TEXT_SIZE ) );
63
64
  private final static boolean DEBUG = false;
65
66
  public TestVariableNameProcessor() {
67
  }
68
69
  @Override
70
  public void start( final Stage stage ) throws Exception {
71
    super.start( stage );
72
73
    final TreeView<String> treeView = createTreeView();
74
    final Map<String, String> definitions = new HashMap<>();
75
76
    populate( treeView.getRoot(), definitions );
77
    injectVariables( definitions );
78
79
    final String text = SOURCE.toString();
80
81
    show( text );
82
83
    long duration = System.nanoTime();
84
85
    // TODO: Test replaceEach (with intercoluated variables) and replaceEachRepeatedly
86
    // (without intercoluation).
87
    final String result = testBorAhoCorasick( text, definitions );
88
89
    duration = System.nanoTime() - duration;
90
91
    show( result );
92
    System.out.println( elapsed( duration ) );
93
94
    System.exit( 0 );
95
  }
96
97
  private void show( final String s ) {
98
    if( DEBUG ) {
99
      System.out.printf( "%s\n\n", s );
100
    }
101
  }
102
103
  private String testBorAhoCorasick(
104
    final String text,
105
    final Map<String, String> definitions ) {
106
    // Create a buffer sufficiently large that re-allocations are minimized.
107
    final StringBuilder sb = new StringBuilder( text.length() << 1 );
108
109
    final TrieBuilder builder = Trie.builder();
110
    builder.onlyWholeWords();
111
    builder.removeOverlaps();
112
113
    final String[] keys = keys( definitions );
114
115
    for( final String key : keys ) {
116
      builder.addKeyword( key );
117
    }
118
119
    final Trie trie = builder.build();
120
    final Collection<Emit> emits = trie.parseText( text );
121
122
    int prevIndex = 0;
123
124
    for( final Emit emit : emits ) {
125
      final int matchIndex = emit.getStart();
126
127
      sb.append( text.substring( prevIndex, matchIndex ) );
128
      sb.append( definitions.get( emit.getKeyword() ) );
129
      prevIndex = emit.getEnd() + 1;
130
    }
131
132
    // Add the remainder of the string (contains no more matches).
133
    sb.append( text.substring( prevIndex ) );
134
135
    return sb.toString();
136
  }
137
138
  private String testStringUtils(
139
    final String text, final Map<String, String> definitions ) {
140
    final String[] keys = keys( definitions );
141
    final String[] values = values( definitions );
142
143
    return StringUtils.replaceEach( text, keys, values );
144
  }
145
146
  private String[] keys( final Map<String, String> definitions ) {
147
    final int size = definitions.size();
148
    return definitions.keySet().toArray( new String[ size ] );
149
  }
150
151
  private String[] values( final Map<String, String> definitions ) {
152
    final int size = definitions.size();
153
    return definitions.values().toArray( new String[ size ] );
154
  }
155
156
  /**
157
   * Decomposes a period of time into days, hours, minutes, seconds,
158
   * milliseconds, and nanoseconds.
159
   *
160
   * @param duration Time in nanoseconds.
161
   *
162
   * @return A non-null, comma-separated string (without newline).
163
   */
164
  public String elapsed( long duration ) {
165
    final TimeUnit scale = NANOSECONDS;
166
167
    long days = scale.toDays( duration );
168
    duration -= DAYS.toMillis( days );
169
    long hours = scale.toHours( duration );
170
    duration -= HOURS.toMillis( hours );
171
    long minutes = scale.toMinutes( duration );
172
    duration -= MINUTES.toMillis( minutes );
173
    long seconds = scale.toSeconds( duration );
174
    duration -= SECONDS.toMillis( seconds );
175
    long millis = scale.toMillis( duration );
176
    duration -= MILLISECONDS.toMillis( seconds );
177
    long nanos = scale.toNanos( duration );
178
179
    return String.format(
180
      "%d days, %d hours, %d minutes, %d seconds, %d millis, %d nanos",
181
      days, hours, minutes, seconds, millis, nanos
182
    );
183
  }
184
185
  private void injectVariables( final Map<String, String> definitions ) {
186
    for( int i = (SOURCE.length() / MATCHES_DIVISOR) + 1; i > 0; i-- ) {
187
      final int r = current().nextInt( 1, SOURCE.length() );
188
      SOURCE.insert( r, randomKey( definitions ) );
189
    }
190
  }
191
192
  private String randomKey( final Map<String, String> map ) {
193
    final Object[] keys = map.keySet().toArray();
194
    final int r = current().nextInt( keys.length );
195
    return keys[ r ].toString();
196
  }
197
198
  private void populate( final TreeItem<String> parent, final Map<String, String> map ) {
199
    for( final TreeItem<String> child : parent.getChildren() ) {
200
      if( child.isLeaf() ) {
201
        final String key = asDefinition( ((VariableTreeItem<String>)child).toPath() );
202
        final String value = child.getValue();
203
204
        map.put( key, value );
205
      } else {
206
        populate( child, map );
207
      }
208
    }
209
  }
210
211
  private String asDefinition( final String key ) {
212
    return "$" + key + "$";
213
  }
214
215
  public static void main( String[] args ) {
216
    launch( args );
217
  }
218
}
1219
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/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 static com.scrivenvar.Constants.SEPARATOR;
31
import static com.scrivenvar.definition.Lists.getFirst;
32
import com.scrivenvar.predicates.strings.ContainsPredicate;
33
import com.scrivenvar.predicates.strings.StartsPredicate;
34
import com.scrivenvar.predicates.strings.StringPredicate;
35
import com.scrivenvar.ui.AbstractPane;
36
import com.scrivenvar.ui.VariableTreeItem;
37
import java.util.List;
38
import javafx.collections.ObservableList;
39
import javafx.scene.Node;
40
import javafx.scene.control.MultipleSelectionModel;
41
import javafx.scene.control.SelectionMode;
42
import javafx.scene.control.TreeItem;
43
import javafx.scene.control.TreeView;
44
45
/**
46
 * Provides a list of variables that can be referenced in the editor.
47
 *
48
 * @author White Magic Software, Ltd.
49
 */
50
public class DefinitionPane extends AbstractPane {
51
52
  private final static String TERMINALS = ":;,.!?-/\\¡¿";
53
54
  private TreeView<String> treeView;
55
56
  /**
57
   * Constructs a definition pane with a given tree view root.
58
   *
59
   * @see YamlTreeAdapter.adapt
60
   * @param root The root of the variable definition tree.
61
   */
62
  public DefinitionPane( final TreeView<String> root ) {
63
    setTreeView( root );
64
    initTreeView();
65
  }
66
67
  /**
68
   * Finds a tree item with a value that exactly matches the given word.
69
   *
70
   * @param trunk The root item containing a list of nodes to search.
71
   * @param word The value of the item to find.
72
   * @param predicate Helps determine whether the node value matches the word.
73
   *
74
   * @return The item that matches the given word, or null if not found.
75
   */
76
  private TreeItem<String> findNode(
77
    final TreeItem<String> trunk,
78
    final StringPredicate predicate ) {
79
    final List<TreeItem<String>> branches = trunk.getChildren();
80
    TreeItem<String> result = null;
81
82
    for( final TreeItem<String> leaf : branches ) {
83
      if( predicate.test( leaf.getValue() ) ) {
84
        result = leaf;
85
        break;
86
      }
87
    }
88
89
    return result;
90
  }
91
92
  /**
93
   * Calls findNode with the EqualsPredicate.
94
   *
95
   * @see findNode( TreeItem, String, Predicate )
96
   * @return The result from findNode.
97
   */
98
  private TreeItem<String> findStartsNode(
99
    final TreeItem<String> trunk,
100
    final String word ) {
101
    return findNode( trunk, new StartsPredicate( word ) );
102
  }
103
104
  /**
105
   * Calls findNode with the ContainsPredicate.
106
   *
107
   * @see findNode( TreeItem, String, Predicate )
108
   * @return The result from findNode.
109
   */
110
  private TreeItem<String> findSubstringNode(
111
    final TreeItem<String> trunk,
112
    final String word ) {
113
    return findNode( trunk, new ContainsPredicate( word ) );
114
  }
115
116
  /**
117
   * Finds a node that matches a prefix and suffix specified by the given path
118
   * variable. The prefix must match a valid node value. The suffix refers to
119
   * the start of a string that matches zero or more children of the node
120
   * specified by the prefix. The algorithm has the following cases:
121
   *
122
   * <ol>
123
   * <li>Path is empty, return first child.</li>
124
   * <li>Path contains a complete match, return corresponding node.</li>
125
   * <li>Path contains a partial match, return nearest node.</li>
126
   * <li>Path contains a complete and partial match, return nearest node.</li>
127
   * </ol>
128
   *
129
   * @param path The word typed by the user, which contains dot-separated node
130
   * names that represent a path within the YAML tree plus a partial variable
131
   * name match (for a node).
132
   *
133
   * @return The node value that starts with the suffix portion of the given
134
   * path, never null.
135
   */
136
  public TreeItem<String> findNode( String path ) {
137
    TreeItem<String> cItem = getTreeRoot();
138
    TreeItem<String> pItem = cItem;
139
140
    int index = path.indexOf( SEPARATOR );
141
142
    while( index >= 0 ) {
143
      final String node = path.substring( 0, index );
144
      path = path.substring( index + 1 );
145
146
      if( (cItem = findStartsNode( cItem, node )) == null ) {
147
        break;
148
      }
149
150
      index = path.indexOf( SEPARATOR );
151
      pItem = cItem;
152
    }
153
154
    // Find the node that starts with whatever the user typed.
155
    cItem = findStartsNode( pItem, path );
156
157
    // If there was no matching node, then find a substring match.
158
    if( cItem == null ) {
159
      cItem = findSubstringNode( pItem, path );
160
    }
161
162
    // If neither starts with nor substring matched a node, revert to the last
163
    // known valid node.
164
    if( cItem == null ) {
165
      cItem = pItem;
166
    }
167
168
    return sanitize( cItem );
169
  }
170
171
  /**
172
   * Returns the leaf that matches the given value. If the value is terminally
173
   * punctuated, the punctuation is removed if no match was found.
174
   *
175
   * @param value The value to find, never null.
176
   *
177
   * @return The leaf that contains the given value, or null if neither the
178
   * original value nor the terminally-trimmed value was found.
179
   */
180
  public VariableTreeItem<String> findLeaf( final String value ) {
181
    final VariableTreeItem<String> root = getTreeRoot();
182
    final VariableTreeItem<String> leaf = root.findLeaf( value );
183
184
    return leaf == null
185
      ? root.findLeaf( rtrimTerminalPunctuation( value ) )
186
      : leaf;
187
  }
188
189
  /**
190
   * Removes punctuation from the end of a string. The character set includes:
191
   * <code>:;,.!?-/\¡¿</code>.
192
   *
193
   * @param s The string to trim, never null.
194
   *
195
   * @return The string trimmed of all terminal characters from the end
196
   */
197
  private String rtrimTerminalPunctuation( final String s ) {
198
    final StringBuilder result = new StringBuilder( s.trim() );
199
200
    while( TERMINALS.contains( "" + result.charAt( result.length() - 1 ) ) ) {
201
      result.setLength( result.length() - 1 );
202
    }
203
204
    return result.toString();
205
  }
206
207
  /**
208
   * Returns the tree root if either item or its first child are null.
209
   *
210
   * @param item The item to make null safe.
211
   *
212
   * @return A non-null TreeItem, possibly the root item (to avoid null).
213
   */
214
  private TreeItem<String> sanitize( final TreeItem<String> item ) {
215
    final TreeItem<String> result = item == getTreeRoot()
216
      ? getFirst( item.getChildren() )
217
      : item;
218
219
    return result == null ? item : result;
220
  }
221
222
  /**
223
   * Expands the node to the root, recursively.
224
   *
225
   * @param <T> The type of tree item to expand (usually String).
226
   * @param node The node to expand.
227
   */
228
  public <T> void expand( final TreeItem<T> node ) {
229
    if( node != null ) {
230
      expand( node.getParent() );
231
232
      if( !node.isLeaf() ) {
233
        node.setExpanded( true );
234
      }
235
    }
236
  }
237
238
  public void select( final TreeItem<String> item ) {
239
    clearSelection();
240
    selectItem( getTreeView().getRow( item ) );
241
  }
242
243
  private void clearSelection() {
244
    getSelectionModel().clearSelection();
245
  }
246
247
  private void selectItem( final int row ) {
248
    getSelectionModel().select( row );
249
  }
250
251
  /**
252
   * Collapses the tree, recursively.
253
   */
254
  public void collapse() {
255
    collapse( getTreeRoot().getChildren() );
256
  }
257
258
  /**
259
   * Collapses the tree, recursively.
260
   *
261
   * @param <T> The type of tree item to expand (usually String).
262
   * @param node The nodes to collapse.
263
   */
264
  private <T> void collapse( ObservableList<TreeItem<T>> nodes ) {
265
    for( final TreeItem<T> node : nodes ) {
266
      node.setExpanded( false );
267
      collapse( node.getChildren() );
268
    }
269
  }
270
271
  private void initTreeView() {
272
    getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
273
  }
274
275
  /**
276
   * Returns the root node to the tree view.
277
   *
278
   * @return getTreeView()
279
   */
280
  public Node getNode() {
281
    return getTreeView();
282
  }
283
284
  private MultipleSelectionModel getSelectionModel() {
285
    return getTreeView().getSelectionModel();
286
  }
287
288
  /**
289
   * Returns the tree view that contains the YAML definition hierarchy.
290
   *
291
   * @return A non-null instance.
292
   */
293
  private TreeView<String> getTreeView() {
294
    return this.treeView;
295
  }
296
297
  /**
298
   * Returns the root of the tree.
299
   *
300
   * @return The first node added to the YAML definition tree.
301
   */
302
  private VariableTreeItem<String> getTreeRoot() {
303
    return (VariableTreeItem<String>)getTreeView().getRoot();
304
  }
305
306
  public <T> boolean isRoot( final TreeItem<T> item ) {
307
    return getTreeRoot().equals( item );
308
  }
309
310
  /**
311
   * Sets the tree view (called by the constructor).
312
   *
313
   * @param treeView
314
   */
315
  private void setTreeView( TreeView<String> treeView ) {
316
    if( treeView != null ) {
317
      this.treeView = treeView;
318
    }
319
  }
320
}
1321
A src/main/java/com/scrivenvar/definition/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.definition;
29
30
import java.util.List;
31
32
/**
33
 * Convenience class that provides a clearer API for obtaining list elements.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public final class Lists {
38
39
  private Lists() {
40
  }
41
42
  /**
43
   * Returns the first item in the given list, or null if not found.
44
   *
45
   * @param <T> The generic list type.
46
   * @param list The list that may have a first item.
47
   *
48
   * @return null if the list is null or there is no first item.
49
   */
50
  public static <T> T getFirst( final List<T> list ) {
51
    return getFirst( list, null );
52
  }
53
54
  /**
55
   * Returns the last item in the given list, or null if not found.
56
   *
57
   * @param <T> The generic list type.
58
   * @param list The list that may have a last item.
59
   *
60
   * @return null if the list is null or there is no last item.
61
   */
62
  public static <T> T getLast( final List<T> list ) {
63
    return getLast( list, null );
64
  }
65
66
  /**
67
   * Returns the first item in the given list, or t if not found.
68
   *
69
   * @param <T> The generic list type.
70
   * @param list The list that may have a first item.
71
   * @param t The default return value.
72
   *
73
   * @return null if the list is null or there is no first item.
74
   */
75
  public static <T> T getFirst( final List<T> list, final T t ) {
76
    return isEmpty( list ) ? t : list.get( 0 );
77
  }
78
79
  /**
80
   * Returns the last item in the given list, or t if not found.
81
   *
82
   * @param <T> The generic list type.
83
   * @param list The list that may have a last item.
84
   * @param t The default return value.
85
   *
86
   * @return null if the list is null or there is no last item.
87
   */
88
  public static <T> T getLast( final List<T> list, final T t ) {
89
    return isEmpty( list ) ? t : list.get( list.size() - 1 );
90
  }
91
92
  /**
93
   * Returns true if the given list is null or empty.
94
   *
95
   * @param <T> The generic list type.
96
   * @param list The list that has a last item.
97
   *
98
   * @return true The list is empty.
99
   */
100
  public static <T> boolean isEmpty( final List<T> list ) {
101
    return list == null || list.isEmpty();
102
  }
103
}
1104
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.editor.HyperlinkModel;
33
import com.scrivenvar.service.events.impl.ButtonOrderPane;
34
import java.nio.file.Path;
35
import javafx.application.Platform;
36
import javafx.beans.binding.Bindings;
37
import javafx.beans.property.SimpleStringProperty;
38
import javafx.beans.property.StringProperty;
39
import javafx.scene.control.ButtonBar.ButtonData;
40
import javafx.scene.control.ButtonType;
41
import javafx.scene.control.Dialog;
42
import javafx.scene.control.DialogPane;
43
import javafx.scene.control.Label;
44
import javafx.stage.Window;
45
import org.tbee.javafx.scene.layout.fxml.MigPane;
46
47
/**
48
 * Dialog to enter a markdown link.
49
 *
50
 * @author Karl Tauber
51
 */
52
public class LinkDialog extends Dialog<String> {
53
54
  private final StringProperty link = new SimpleStringProperty();
55
56
  public LinkDialog( final Window owner, final HyperlinkModel hyperlink, final Path basePath ) {
57
    setTitle( Messages.get( "LinkDialog.title" ) );
58
    initOwner( owner );
59
    setResizable( true );
60
61
    initComponents();
62
63
    setDialogPane( new ButtonOrderPane() );
64
65
    final DialogPane dialog = getDialogPane();
66
    dialog.setContent( pane );
67
    dialog.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL );
68
69
    dialog.lookupButton( ButtonType.OK ).disableProperty().bind(
70
      urlField.escapedTextProperty().isEmpty() );
71
72
    textField.setText( hyperlink.getText() );
73
    urlField.setText( hyperlink.getUrl() );
74
    titleField.setText( hyperlink.getTitle() );
75
76
    link.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() )
77
      .then( Bindings.format( "[%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) )
78
      .otherwise( Bindings.when( textField.escapedTextProperty().isNotEmpty() )
79
        .then( Bindings.format( "[%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) )
80
        .otherwise( urlField.escapedTextProperty() ) ) );
81
82
    setResultConverter( dialogButton -> {
83
      ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null;
84
      return (data == ButtonData.OK_DONE) ? link.get() : null;
85
    } );
86
87
    Platform.runLater( () -> {
88
      urlField.requestFocus();
89
      urlField.selectRange( 0, urlField.getLength() );
90
    } );
91
  }
92
93
  private void initComponents() {
94
    // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
95
    pane = new MigPane();
96
    Label urlLabel = new Label();
97
    urlField = new EscapeTextField();
98
    Label textLabel = new Label();
99
    textField = new EscapeTextField();
100
    Label titleLabel = new Label();
101
    titleField = new EscapeTextField();
102
103
    //======== pane ========
104
    {
105
      pane.setCols( "[shrink 0,fill][300,grow,fill][fill][fill]" );
106
      pane.setRows( "[][][][]" );
107
108
      //---- urlLabel ----
109
      urlLabel.setText( Messages.get( "LinkDialog.urlLabel.text" ) );
110
      pane.add( urlLabel, "cell 0 0" );
111
112
      //---- urlField ----
113
      urlField.setEscapeCharacters( "()" );
114
      pane.add( urlField, "cell 1 0" );
115
116
      //---- textLabel ----
117
      textLabel.setText( Messages.get( "LinkDialog.textLabel.text" ) );
118
      pane.add( textLabel, "cell 0 1" );
119
120
      //---- textField ----
121
      textField.setEscapeCharacters( "[]" );
122
      pane.add( textField, "cell 1 1 3 1" );
123
124
      //---- titleLabel ----
125
      titleLabel.setText( Messages.get( "LinkDialog.titleLabel.text" ) );
126
      pane.add( titleLabel, "cell 0 2" );
127
      pane.add( titleField, "cell 1 2 3 1" );
128
    }
129
    // JFormDesigner - End of component initialization  //GEN-END:initComponents
130
  }
131
132
  // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
133
  private MigPane pane;
134
  private EscapeTextField urlField;
135
  private EscapeTextField textField;
136
  private EscapeTextField titleField;
137
  // JFormDesigner - End of variables declaration  //GEN-END:variables
138
}
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/editor/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.editor;
29
30
import com.scrivenvar.ui.AbstractPane;
31
import java.nio.file.Path;
32
import java.util.function.Consumer;
33
import javafx.application.Platform;
34
import javafx.beans.property.ObjectProperty;
35
import javafx.beans.property.ReadOnlyDoubleProperty;
36
import javafx.beans.property.ReadOnlyDoubleWrapper;
37
import javafx.beans.property.SimpleObjectProperty;
38
import javafx.beans.value.ChangeListener;
39
import javafx.event.Event;
40
import javafx.scene.control.ScrollPane;
41
import javafx.scene.input.InputEvent;
42
import org.fxmisc.flowless.VirtualizedScrollPane;
43
import org.fxmisc.richtext.StyleClassedTextArea;
44
import org.fxmisc.undo.UndoManager;
45
import org.fxmisc.wellbehaved.event.EventPattern;
46
import org.fxmisc.wellbehaved.event.InputMap;
47
import static org.fxmisc.wellbehaved.event.InputMap.consume;
48
import org.fxmisc.wellbehaved.event.Nodes;
49
50
/**
51
 * Represents common editing features for various types of text editors.
52
 *
53
 * @author White Magic Software, Ltd.
54
 */
55
public class EditorPane extends AbstractPane {
56
57
  private StyleClassedTextArea editor;
58
  private VirtualizedScrollPane<StyleClassedTextArea> scrollPane;
59
  private final ReadOnlyDoubleWrapper scrollY = new ReadOnlyDoubleWrapper();
60
  private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
61
62
  private String lineSeparator = getLineSeparator();
63
64
  /**
65
   * Set when entering variable edit mode; retrieved upon exiting.
66
   */
67
  private InputMap<InputEvent> nodeMap;
68
69
  @Override
70
  public void requestFocus() {
71
    Platform.runLater( () -> getEditor().requestFocus() );
72
  }
73
74
  public void undo() {
75
    getUndoManager().undo();
76
  }
77
78
  public void redo() {
79
    getUndoManager().redo();
80
  }
81
82
  public UndoManager getUndoManager() {
83
    return getEditor().getUndoManager();
84
  }
85
86
  public String getText() {
87
    String text = getEditor().getText();
88
89
    if( !this.lineSeparator.equals( "\n" ) ) {
90
      text = text.replace( "\n", this.lineSeparator );
91
    }
92
93
    return text;
94
  }
95
96
  public void setText( final String text ) {
97
    this.lineSeparator = determineLineSeparator( text );
98
    getEditor().deselect();
99
    getEditor().replaceText( text );
100
    getUndoManager().mark();
101
  }
102
103
  /**
104
   * Call to hook into changes to the text area.
105
   *
106
   * @param listener Receives editor text change events.
107
   */
108
  public void addChangeListener( final ChangeListener<? super String> listener ) {
109
    getEditor().textProperty().addListener( listener );
110
  }
111
112
  /**
113
   * Call to listen for when the caret moves to another paragraph.
114
   * 
115
   * @param listener Receives paragraph change events.
116
   */
117
  public void addCaretParagraphListener( final ChangeListener<? super Integer> listener ) {
118
    getEditor().currentParagraphProperty().addListener( listener );
119
  }
120
121
  /**
122
   * This method adds listeners to editor events.
123
   *
124
   * @param <T> The event type.
125
   * @param <U> The consumer type for the given event type.
126
   * @param event The event of interest.
127
   * @param consumer The method to call when the event happens.
128
   */
129
  public <T extends Event, U extends T> void addEventListener(
130
    final EventPattern<? super T, ? extends U> event,
131
    final Consumer<? super U> consumer ) {
132
    Nodes.addInputMap( getEditor(), consume( event, consumer ) );
133
  }
134
135
  /**
136
   * This method adds listeners to editor events that can be removed without
137
   * affecting the original listeners (i.e., the original lister is restored on
138
   * a call to removeEventListener).
139
   *
140
   * @param map The map of methods to events.
141
   */
142
  @SuppressWarnings( "unchecked" )
143
  public void addEventListener( final InputMap<InputEvent> map ) {
144
    this.nodeMap = (InputMap<InputEvent>)getInputMap();
145
    Nodes.addInputMap( getEditor(), map );
146
  }
147
148
  /**
149
   * This method removes listeners to editor events and restores the default
150
   * handler.
151
   *
152
   * @param map The map of methods to events.
153
   */
154
  public void removeEventListener( final InputMap<InputEvent> map ) {
155
    Nodes.removeInputMap( getEditor(), map );
156
    Nodes.addInputMap( getEditor(), this.nodeMap );
157
  }
158
159
  /**
160
   * Returns the value for "org.fxmisc.wellbehaved.event.inputmap".
161
   *
162
   * @return An input map of input events.
163
   */
164
  private Object getInputMap() {
165
    return getEditor().getProperties().get( getInputMapKey() );
166
  }
167
168
  /**
169
   * Returns the hashmap key entry for the input map.
170
   *
171
   * @return "org.fxmisc.wellbehaved.event.inputmap"
172
   */
173
  private String getInputMapKey() {
174
    return "org.fxmisc.wellbehaved.event.inputmap";
175
  }
176
177
  public void scrollToTop() {
178
    getEditor().moveTo( 0 );
179
  }
180
181
  private void setEditor( StyleClassedTextArea textArea ) {
182
    this.editor = textArea;
183
  }
184
185
  public synchronized StyleClassedTextArea getEditor() {
186
    if( this.editor == null ) {
187
      setEditor( createTextArea() );
188
    }
189
190
    return this.editor;
191
  }
192
193
  /**
194
   * Returns the scroll pane that contains the text area.
195
   *
196
   * @return The scroll pane that contains the content to edit.
197
   */
198
  public synchronized VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() {
199
    if( this.scrollPane == null ) {
200
      this.scrollPane = createScrollPane();
201
    }
202
203
    return this.scrollPane;
204
  }
205
206
  protected VirtualizedScrollPane<StyleClassedTextArea> createScrollPane() {
207
    final VirtualizedScrollPane<StyleClassedTextArea> pane = new VirtualizedScrollPane<>( getEditor() );
208
    pane.setVbarPolicy( ScrollPane.ScrollBarPolicy.ALWAYS );
209
210
    return pane;
211
  }
212
213
  protected StyleClassedTextArea createTextArea() {
214
    return new StyleClassedTextArea( false );
215
  }
216
217
  public double getScrollY() {
218
    return this.scrollY.get();
219
  }
220
221
  protected void setScrollY( double scrolled ) {
222
    this.scrollY.set( scrolled );
223
  }
224
225
  public ReadOnlyDoubleProperty scrollYProperty() {
226
    return this.scrollY.getReadOnlyProperty();
227
  }
228
229
  public Path getPath() {
230
    return this.path.get();
231
  }
232
233
  public void setPath( final Path path ) {
234
    this.path.set( path );
235
  }
236
237
  public ObjectProperty<Path> pathProperty() {
238
    return this.path;
239
  }
240
241
  private String getLineSeparator() {
242
    final String separator = getOptions().getLineSeparator();
243
244
    return (separator != null)
245
      ? separator
246
      : System.lineSeparator();
247
  }
248
249
  private String determineLineSeparator( final String s ) {
250
    final int length = s.length();
251
252
    // TODO: Looping backwards will probably detect a newline sooner.
253
    for( int i = 0; i < length; i++ ) {
254
      char ch = s.charAt( i );
255
      if( ch == '\n' ) {
256
        return (i > 0 && s.charAt( i - 1 ) == '\r') ? "\r\n" : "\n";
257
      }
258
    }
259
260
    return getLineSeparator();
261
  }
262
}
1263
A src/main/java/com/scrivenvar/editor/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.editor;
29
30
import com.vladsch.flexmark.ast.Link;
31
32
/**
33
 * Represents the model for a hyperlink: text and url text.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public class HyperlinkModel {
38
39
  private String text;
40
  private String url;
41
  private String title;
42
43
  /**
44
   * Constructs a new hyperlink model in Markdown format by default with no
45
   * title (i.e., tooltip).
46
   *
47
   * @param text The hyperlink text displayed (e.g., displayed to the user).
48
   * @param url The destination URL (e.g., when clicked).
49
   */
50
  public HyperlinkModel( final String text, final String url ) {
51
    this( text, url, null );
52
  }
53
54
  /**
55
   * Constructs a new hyperlink model for the given AST link.
56
   * 
57
   * @param link A markdown link.
58
   */
59
  public HyperlinkModel( final Link link ) {
60
    this(
61
      link.getText().toString(),
62
      link.getUrl().toString(),
63
      link.getTitle().toString()
64
    );
65
  }
66
67
  /**
68
   * Constructs a new hyperlink model in Markdown format by default.
69
   *
70
   * @param text The hyperlink text displayed (e.g., displayed to the user).
71
   * @param url The destination URL (e.g., when clicked).
72
   * @param title The hyperlink title (e.g., shown as a tooltip).
73
   */
74
  public HyperlinkModel( final String text, final String url, final String title ) {
75
    setText( text );
76
    setUrl( url );
77
    setTitle( title );
78
  }
79
80
  /**
81
   * Returns the string in Markdown format by default.
82
   *
83
   * @return A markdown version of the hyperlink.
84
   */
85
  @Override
86
  public String toString() {
87
    String format = "%s%s%s";
88
89
    if( hasText() ) {
90
      format = "[%s]" + (hasTitle() ? "(%s \"%s\")" : "(%s%s)");
91
    }
92
93
    // Becomes ""+URL+"" if no text is set.
94
    // Becomes [TITLE]+(URL)+"" if no title is set.
95
    // Becomes [TITLE]+(URL+ \"TITLE\") if title is set.
96
    return String.format( format, getText(), getUrl(), getTitle() );
97
  }
98
99
  public final void setText( final String text ) {
100
    this.text = nullSafe( text );
101
  }
102
103
  public final void setUrl( final String url ) {
104
    this.url = nullSafe( url );
105
  }
106
107
  public final void setTitle( final String title ) {
108
    this.title = nullSafe( title );
109
  }
110
111
  /**
112
   * Answers whether text has been set for the hyperlink.
113
   *
114
   * @return true This is a text link.
115
   */
116
  public boolean hasText() {
117
    return !getText().isEmpty();
118
  }
119
120
  /**
121
   * Answers whether a title (tooltip) has been set for the hyperlink.
122
   *
123
   * @return true There is a title.
124
   */
125
  public boolean hasTitle() {
126
    return !getTitle().isEmpty();
127
  }
128
129
  public String getText() {
130
    return this.text;
131
  }
132
133
  public String getUrl() {
134
    return this.url;
135
  }
136
137
  public String getTitle() {
138
    return this.title;
139
  }
140
141
  private String nullSafe( final String s ) {
142
    return s == null ? "" : s;
143
  }
144
}
1145
A src/main/java/com/scrivenvar/editor/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.editor;
29
30
import com.vladsch.flexmark.ast.Link;
31
import com.vladsch.flexmark.ast.Node;
32
import com.vladsch.flexmark.ast.NodeVisitor;
33
import com.vladsch.flexmark.ast.VisitHandler;
34
35
/**
36
 * @author White Magic Software, Ltd.
37
 */
38
public class LinkVisitor {
39
40
  private NodeVisitor visitor;
41
  private Link link;
42
  private final int offset;
43
44
  /**
45
   * Creates a hyperlink given an offset into a paragraph and the markdown AST
46
   * link node.
47
   *
48
   * @param index Index into the paragraph that indicates the hyperlink to
49
   * change.
50
   */
51
  public LinkVisitor( final int index ) {
52
    this.offset = index;
53
  }
54
55
  public Link process( final Node root ) {
56
    getVisitor().visit( root );
57
    return getLink();
58
  }
59
60
  /**
61
   *
62
   * @param link Not null.
63
   */
64
  private void visit( final Link link ) {
65
    final int began = link.getStartOffset();
66
    final int ended = link.getEndOffset();
67
    final int index = getOffset();
68
69
    if( index >= began && index <= ended ) {
70
      setLink( link );
71
    }
72
  }
73
74
  private synchronized NodeVisitor getVisitor() {
75
    if( this.visitor == null ) {
76
      this.visitor = createVisitor();
77
    }
78
79
    return this.visitor;
80
  }
81
82
  protected NodeVisitor createVisitor() {
83
    return new NodeVisitor(
84
      new VisitHandler<>( Link.class, LinkVisitor.this::visit ) );
85
  }
86
87
  private Link getLink() {
88
    return this.link;
89
  }
90
91
  private void setLink( final Link link ) {
92
    this.link = link;
93
  }
94
95
  public int getOffset() {
96
    return this.offset;
97
  }
98
}
199
A src/main/java/com/scrivenvar/editor/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.editor;
29
30
import static com.scrivenvar.Constants.STYLESHEET_EDITOR;
31
import com.scrivenvar.dialogs.ImageDialog;
32
import com.scrivenvar.dialogs.LinkDialog;
33
import com.scrivenvar.processors.MarkdownProcessor;
34
import com.scrivenvar.util.Utils;
35
import com.vladsch.flexmark.ast.Link;
36
import com.vladsch.flexmark.ast.Node;
37
import java.nio.file.Path;
38
import java.util.regex.Matcher;
39
import java.util.regex.Pattern;
40
import javafx.beans.value.ObservableValue;
41
import javafx.scene.control.Dialog;
42
import javafx.scene.control.IndexRange;
43
import static javafx.scene.input.KeyCode.ENTER;
44
import javafx.scene.input.KeyEvent;
45
import javafx.stage.Window;
46
import org.fxmisc.richtext.StyleClassedTextArea;
47
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
48
49
/**
50
 * Markdown editor pane.
51
 *
52
 * @author Karl Tauber and White Magic Software, Ltd.
53
 */
54
public class MarkdownEditorPane extends EditorPane {
55
56
  private static final Pattern AUTO_INDENT_PATTERN = Pattern.compile(
57
    "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" );
58
59
  public MarkdownEditorPane() {
60
    initEditor();
61
  }
62
63
  private void initEditor() {
64
    final StyleClassedTextArea textArea = getEditor();
65
66
    textArea.setWrapText( true );
67
    textArea.getStyleClass().add( "markdown-editor" );
68
    textArea.getStylesheets().add( STYLESHEET_EDITOR );
69
70
    addEventListener( keyPressed( ENTER ), this::enterPressed );
71
72
    // TODO: Wait for implementation that allows cutting lines, not paragraphs.
73
//    addEventListener( keyPressed( X, SHORTCUT_DOWN ), this::cutLine );
74
  }
75
76
  public ObservableValue<String> markdownProperty() {
77
    return getEditor().textProperty();
78
  }
79
80
  private void enterPressed( final KeyEvent e ) {
81
    final StyleClassedTextArea textArea = getEditor();
82
    final String currentLine = textArea.getText( textArea.getCurrentParagraph() );
83
    final Matcher matcher = AUTO_INDENT_PATTERN.matcher( currentLine );
84
85
    String newText = "\n";
86
87
    if( matcher.matches() ) {
88
      if( !matcher.group( 2 ).isEmpty() ) {
89
        // indent new line with same whitespace characters and list markers as current line
90
        newText = newText.concat( matcher.group( 1 ) );
91
      } else {
92
        // current line contains only whitespace characters and list markers
93
        // --> empty current line
94
        final int caretPosition = textArea.getCaretPosition();
95
        textArea.selectRange( caretPosition - currentLine.length(), caretPosition );
96
      }
97
    }
98
99
    textArea.replaceSelection( newText );
100
  }
101
102
  public void surroundSelection( final String leading, final String trailing ) {
103
    surroundSelection( leading, trailing, null );
104
  }
105
106
  public void surroundSelection( String leading, String trailing, final String hint ) {
107
    final StyleClassedTextArea textArea = getEditor();
108
109
    // Note: not using textArea.insertText() to insert leading and trailing
110
    // because this would add two changes to undo history
111
    final IndexRange selection = textArea.getSelection();
112
    int start = selection.getStart();
113
    int end = selection.getEnd();
114
115
    final String selectedText = textArea.getSelectedText();
116
117
    // remove leading and trailing whitespaces from selected text
118
    String trimmedSelectedText = selectedText.trim();
119
    if( trimmedSelectedText.length() < selectedText.length() ) {
120
      start += selectedText.indexOf( trimmedSelectedText );
121
      end = start + trimmedSelectedText.length();
122
    }
123
124
    // remove leading whitespaces from leading text if selection starts at zero
125
    if( start == 0 ) {
126
      leading = Utils.ltrim( leading );
127
    }
128
129
    // remove trailing whitespaces from trailing text if selection ends at text end
130
    if( end == textArea.getLength() ) {
131
      trailing = Utils.rtrim( trailing );
132
    }
133
134
    // remove leading line separators from leading text
135
    // if there are line separators before the selected text
136
    if( leading.startsWith( "\n" ) ) {
137
      for( int i = start - 1; i >= 0 && leading.startsWith( "\n" ); i-- ) {
138
        if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
139
          break;
140
        }
141
        leading = leading.substring( 1 );
142
      }
143
    }
144
145
    // remove trailing line separators from trailing or leading text
146
    // if there are line separators after the selected text
147
    final boolean trailingIsEmpty = trailing.isEmpty();
148
    String str = trailingIsEmpty ? leading : trailing;
149
150
    if( str.endsWith( "\n" ) ) {
151
      final int length = textArea.getLength();
152
153
      for( int i = end; i < length && str.endsWith( "\n" ); i++ ) {
154
        if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
155
          break;
156
        }
157
158
        str = str.substring( 0, str.length() - 1 );
159
      }
160
161
      if( trailingIsEmpty ) {
162
        leading = str;
163
      } else {
164
        trailing = str;
165
      }
166
    }
167
168
    int selStart = start + leading.length();
169
    int selEnd = end + leading.length();
170
171
    // insert hint text if selection is empty
172
    if( hint != null && trimmedSelectedText.isEmpty() ) {
173
      trimmedSelectedText = hint;
174
      selEnd = selStart + hint.length();
175
    }
176
177
    // prevent undo merging with previous text entered by user
178
    getUndoManager().preventMerge();
179
180
    // replace text and update selection
181
    textArea.replaceText( start, end, leading + trimmedSelectedText + trailing );
182
    textArea.selectRange( selStart, selEnd );
183
  }
184
185
  /**
186
   * Returns one of: selected text, word under cursor, or parsed hyperlink from
187
   * the markdown AST.
188
   *
189
   * @return
190
   */
191
  private HyperlinkModel getHyperlink() {
192
    final StyleClassedTextArea textArea = getEditor();
193
    final String selectedText = textArea.getSelectedText();
194
195
    // Get the current paragraph, convert to Markdown nodes.
196
    final MarkdownProcessor mp = new MarkdownProcessor( null );
197
    final int p = textArea.getCurrentParagraph();
198
    final String paragraph = textArea.getText( p );
199
    final Node node = mp.toNode( paragraph );
200
    final LinkVisitor visitor = new LinkVisitor( textArea.getCaretColumn() );
201
    final Link link = visitor.process( node );
202
203
    if( link != null ) {
204
      textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() );
205
    }
206
207
    final HyperlinkModel model = createHyperlinkModel(
208
      link, selectedText, "https://website.com"
209
    );
210
211
    return model;
212
  }
213
214
  private HyperlinkModel createHyperlinkModel(
215
    final Link link, final String selection, final String url ) {
216
217
    return link == null
218
      ? new HyperlinkModel( selection, url )
219
      : new HyperlinkModel( link );
220
  }
221
222
  private Path getParentPath() {
223
    final Path parentPath = getPath();
224
    return (parentPath != null) ? parentPath.getParent() : null;
225
  }
226
227
  private Dialog<String> createLinkDialog() {
228
    return new LinkDialog( getWindow(), getHyperlink(), getParentPath() );
229
  }
230
231
  private Dialog<String> createImageDialog() {
232
    return new ImageDialog( getWindow(), getParentPath() );
233
  }
234
235
  private void insertObject( final Dialog<String> dialog ) {
236
    dialog.showAndWait().ifPresent( result -> {
237
      getEditor().replaceSelection( result );
238
    } );
239
  }
240
241
  public void insertLink() {
242
    insertObject( createLinkDialog() );
243
  }
244
245
  public void insertImage() {
246
    insertObject( createImageDialog() );
247
  }
248
249
  private Window getWindow() {
250
    return getScrollPane().getScene().getWindow();
251
  }
252
}
1253
A src/main/java/com/scrivenvar/editor/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.editor;
29
30
import static com.scrivenvar.Constants.SEPARATOR;
31
import com.scrivenvar.FileEditorTabPane;
32
import com.scrivenvar.Services;
33
import com.scrivenvar.decorators.VariableDecorator;
34
import com.scrivenvar.decorators.YamlVariableDecorator;
35
import com.scrivenvar.definition.DefinitionPane;
36
import static com.scrivenvar.definition.Lists.getFirst;
37
import static com.scrivenvar.definition.Lists.getLast;
38
import com.scrivenvar.service.Settings;
39
import com.scrivenvar.ui.VariableTreeItem;
40
import static java.lang.Character.isSpaceChar;
41
import static java.lang.Character.isWhitespace;
42
import static java.lang.Math.min;
43
import java.util.function.Consumer;
44
import javafx.collections.ObservableList;
45
import javafx.event.Event;
46
import javafx.scene.control.IndexRange;
47
import javafx.scene.control.TreeItem;
48
import javafx.scene.input.InputEvent;
49
import javafx.scene.input.KeyCode;
50
import static javafx.scene.input.KeyCode.AT;
51
import static javafx.scene.input.KeyCode.DIGIT2;
52
import static javafx.scene.input.KeyCode.ENTER;
53
import static javafx.scene.input.KeyCode.MINUS;
54
import static javafx.scene.input.KeyCode.SPACE;
55
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
56
import static javafx.scene.input.KeyCombination.SHIFT_DOWN;
57
import javafx.scene.input.KeyEvent;
58
import org.fxmisc.richtext.StyledTextArea;
59
import org.fxmisc.wellbehaved.event.EventPattern;
60
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
61
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
62
import org.fxmisc.wellbehaved.event.InputMap;
63
import static org.fxmisc.wellbehaved.event.InputMap.consume;
64
import static org.fxmisc.wellbehaved.event.InputMap.sequence;
65
66
/**
67
 * Provides the logic for injecting variable names within the editor.
68
 *
69
 * @author White Magic Software, Ltd.
70
 */
71
public class VariableNameInjector {
72
73
  public static final int DEFAULT_MAX_VAR_LENGTH = 64;
74
75
  private static final int NO_DIFFERENCE = -1;
76
77
  private final Settings settings = Services.load( Settings.class );
78
79
  /**
80
   * Used to capture keyboard events once the user presses @.
81
   */
82
  private InputMap<InputEvent> keyboardMap;
83
84
  private FileEditorTabPane fileEditorPane;
85
  private DefinitionPane definitionPane;
86
87
  /**
88
   * Position of the variable in the text when in variable mode (0 by default).
89
   */
90
  private int initialCaretPosition;
91
92
  public VariableNameInjector(
93
    final FileEditorTabPane editorPane,
94
    final DefinitionPane definitionPane ) {
95
    setFileEditorPane( editorPane );
96
    setDefinitionPane( definitionPane );
97
98
    initKeyboardEventListeners();
99
  }
100
101
  /**
102
   * Traps keys for performing various short-cut tasks, such as @-mode variable
103
   * insertion and control+space for variable autocomplete.
104
   *
105
   * @ key is pressed, a new keyboard map is inserted in place of the current
106
   * map -- this class goes into "variable edit mode" (a.k.a. vMode).
107
   *
108
   * @see createKeyboardMap()
109
   */
110
  private void initKeyboardEventListeners() {
111
    addEventListener( keyPressed( SPACE, CONTROL_DOWN ), this::autocomplete );
112
113
    // @ key in Linux?
114
    addEventListener( keyPressed( DIGIT2, SHIFT_DOWN ), this::vMode );
115
    // @ key in Windows.
116
    addEventListener( keyPressed( AT ), this::vMode );
117
  }
118
119
  /**
120
   * The @ symbol is a short-cut to inserting a YAML variable reference.
121
   *
122
   * @param e Superfluous information about the key that was pressed.
123
   */
124
  private void vMode( KeyEvent e ) {
125
    setInitialCaretPosition();
126
    vModeStart();
127
    vModeAutocomplete();
128
  }
129
130
  /**
131
   * Receives key presses until the user completes the variable selection. This
132
   * allows the arrow keys to be used for selecting variables.
133
   *
134
   * @param e The key that was pressed.
135
   */
136
  private void vModeKeyPressed( KeyEvent e ) {
137
    final KeyCode keyCode = e.getCode();
138
139
    switch( keyCode ) {
140
      case BACK_SPACE:
141
        // Don't decorate the variable upon exiting vMode.
142
        vModeBackspace();
143
        break;
144
145
      case ESCAPE:
146
        // Don't decorate the variable upon exiting vMode.
147
        vModeStop();
148
        break;
149
150
      case ENTER:
151
      case PERIOD:
152
      case RIGHT:
153
      case END:
154
        // Stop at a leaf node, ENTER means accept.
155
        if( vModeConditionalComplete() && keyCode == ENTER ) {
156
          vModeStop();
157
158
          // Decorate the variable upon exiting vMode.
159
          decorateVariable();
160
        }
161
        break;
162
163
      case UP:
164
        cyclePathPrev();
165
        break;
166
167
      case DOWN:
168
        cyclePathNext();
169
        break;
170
171
      default:
172
        vModeFilterKeyPressed( e );
173
        break;
174
    }
175
176
    e.consume();
177
  }
178
179
  private void vModeBackspace() {
180
    deleteSelection();
181
182
    // Break out of variable mode by back spacing to the original position.
183
    if( getCurrentCaretPosition() > getInitialCaretPosition() ) {
184
      vModeAutocomplete();
185
    } else {
186
      vModeStop();
187
    }
188
  }
189
190
  /**
191
   * Updates the text with the path selected (or typed) by the user.
192
   */
193
  private void vModeAutocomplete() {
194
    final TreeItem<String> node = getCurrentNode();
195
196
    if( !node.isLeaf() ) {
197
      final String word = getLastPathWord();
198
      final String label = node.getValue();
199
      final int delta = difference( label, word );
200
      final String remainder = delta == NO_DIFFERENCE
201
        ? label
202
        : label.substring( delta );
203
204
      final StyledTextArea textArea = getEditor();
205
      final int posBegan = getCurrentCaretPosition();
206
      final int posEnded = posBegan + remainder.length();
207
208
      textArea.replaceSelection( remainder );
209
210
      if( posEnded - posBegan > 0 ) {
211
        textArea.selectRange( posEnded, posBegan );
212
      }
213
214
      expand( node );
215
    }
216
  }
217
218
  /**
219
   * Only variable name keys can pass through the filter. This is called when
220
   * the user presses a key.
221
   *
222
   * @param e The key that was pressed.
223
   */
224
  private void vModeFilterKeyPressed( final KeyEvent e ) {
225
    if( isVariableNameKey( e ) ) {
226
      typed( e.getText() );
227
    }
228
  }
229
230
  /**
231
   * Performs an autocomplete depending on whether the user has finished typing
232
   * in a word. If there is a selected range, then this will complete the most
233
   * recent word and jump to the next child.
234
   *
235
   * @return true The auto-completed node was a terminal node.
236
   */
237
  private boolean vModeConditionalComplete() {
238
    acceptPath();
239
240
    final TreeItem<String> node = getCurrentNode();
241
    final boolean terminal = isTerminal( node );
242
243
    if( !terminal ) {
244
      typed( SEPARATOR );
245
    }
246
247
    return terminal;
248
  }
249
250
  /**
251
   * Pressing control+space will find a node that matches the current word and
252
   * substitute the YAML variable reference. This is called when the user is not
253
   * editing in vMode.
254
   *
255
   * @param e Ignored -- it can only be Ctrl+Space.
256
   */
257
  private void autocomplete( KeyEvent e ) {
258
    final String paragraph = getCaretParagraph();
259
    final int[] boundaries = getWordBoundaries( paragraph );
260
    final String word = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] );
261
262
    final VariableTreeItem<String> leaf = findLeaf( word );
263
264
    if( leaf != null ) {
265
      replaceText( boundaries[ 0 ], boundaries[ 1 ], leaf.toPath() );
266
      decorateVariable();
267
      expand( leaf );
268
    }
269
  }
270
271
  /**
272
   * Called when autocomplete finishes on a valid leaf or when the user presses
273
   * Enter to finish manual autocomplete.
274
   */
275
  private void decorateVariable() {
276
    // A little bit of duplication...
277
    final String paragraph = getCaretParagraph();
278
    final int[] boundaries = getWordBoundaries( paragraph );
279
    final String old = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] );
280
281
    final String newVariable = getVariableDecorator().decorate( old );
282
283
    final int posEnded = getCurrentCaretPosition();
284
    final int posBegan = posEnded - old.length();
285
286
    getEditor().replaceText( posBegan, posEnded, newVariable );
287
  }
288
289
  /**
290
   * Updates the text at the given position within the current paragraph.
291
   *
292
   * @param posBegan The starting index in the paragraph text to replace.
293
   * @param posEnded The ending index in the paragraph text to replace.
294
   * @param text Overwrite the paragraph substring with this text.
295
   */
296
  private void replaceText(
297
    final int posBegan, final int posEnded, final String text ) {
298
    final int p = getCurrentParagraph();
299
300
    getEditor().replaceText( p, posBegan, p, posEnded, text );
301
  }
302
303
  /**
304
   * Returns the caret's current paragraph position.
305
   *
306
   * @return A number greater than or equal to 0.
307
   */
308
  private int getCurrentParagraph() {
309
    return getEditor().getCurrentParagraph();
310
  }
311
312
  /**
313
   * Returns current word boundary indexes into the current paragraph, including
314
   * punctuation.
315
   *
316
   * @param p The paragraph wherein to hunt word boundaries.
317
   * @param offset The offset into the paragraph to begin scanning left and
318
   * right.
319
   *
320
   * @return The starting and ending index of the word closest to the caret.
321
   */
322
  private int[] getWordBoundaries( final String p, final int offset ) {
323
    // Remove dashes, but retain hyphens. Retain same number of characters
324
    // to preserve relative indexes.
325
    final String paragraph = p.replace( "---", "   " ).replace( "--", "  " );
326
327
    return getWordAt( paragraph, offset );
328
  }
329
330
  /**
331
   * Helper method to get the word boundaries for the current paragraph.
332
   *
333
   * @param paragraph
334
   *
335
   * @return
336
   */
337
  private int[] getWordBoundaries( final String paragraph ) {
338
    return getWordBoundaries( paragraph, getCurrentCaretColumn() );
339
  }
340
341
  /**
342
   * Given an arbitrary offset into a string, this returns the word at that
343
   * index. The inputs and outputs include:
344
   *
345
   * <ul>
346
   * <li>surrounded by space: <code>hello | world!</code> ("");</li>
347
   * <li>end of word: <code>hello| world!</code> ("hello");</li>
348
   * <li>start of a word: <code>hello |world!</code> ("world!");</li>
349
   * <li>within a word: <code>hello wo|rld!</code> ("world!");</li>
350
   * <li>end of a paragraph: <code>hello world!|</code> ("world!");</li>
351
   * <li>start of a paragraph: <code>|hello world!</code> ("hello!"); or</li>
352
   * <li>after punctuation: <code>hello world!|</code> ("world!").</li>
353
   * </ul>
354
   *
355
   * @param p The string to scan for a word.
356
   * @param offset The offset within s to begin searching for the nearest word
357
   * boundary, must not be out of bounds of s.
358
   *
359
   * @return The word in s at the offset.
360
   *
361
   * @see getWordBegan( String, int )
362
   * @see getWordEnded( String, int )
363
   */
364
  private int[] getWordAt( final String p, final int offset ) {
365
    return new int[]{ getWordBegan( p, offset ), getWordEnded( p, offset ) };
366
  }
367
368
  /**
369
   * Returns the index into s where a word begins.
370
   *
371
   * @param s Never null.
372
   * @param offset Index into s to begin searching backwards for a word
373
   * boundary.
374
   *
375
   * @return The index where a word begins.
376
   */
377
  private int getWordBegan( final String s, int offset ) {
378
    while( offset > 0 && isBoundary( s.charAt( offset - 1 ) ) ) {
379
      offset--;
380
    }
381
382
    return offset;
383
  }
384
385
  /**
386
   * Returns the index into s where a word ends.
387
   *
388
   * @param s Never null.
389
   * @param offset Index into s to begin searching forwards for a word boundary.
390
   *
391
   * @return The index where a word ends.
392
   */
393
  private int getWordEnded( final String s, int offset ) {
394
    final int length = s.length();
395
396
    while( offset < length && isBoundary( s.charAt( offset ) ) ) {
397
      offset++;
398
    }
399
400
    return offset;
401
  }
402
403
  /**
404
   * Returns true if the given character can be reasonably expected to be part
405
   * of a word, including punctuation marks.
406
   *
407
   * @param c The character to compare.
408
   *
409
   * @return false The character is a space character.
410
   */
411
  private boolean isBoundary( final char c ) {
412
    return !isSpaceChar( c );
413
  }
414
415
  /**
416
   * Returns the text for the paragraph that contains the caret.
417
   *
418
   * @return A non-null string, possibly empty.
419
   */
420
  private String getCaretParagraph() {
421
    return getEditor().getText( getCurrentParagraph() );
422
  }
423
424
  /**
425
   * Returns true if the node has children that can be selected (i.e., any
426
   * non-leaves).
427
   *
428
   * @param <T> The type that the TreeItem contains.
429
   * @param node The node to test for terminality.
430
   *
431
   * @return true The node has one branch and its a leaf.
432
   */
433
  private <T> boolean isTerminal( final TreeItem<T> node ) {
434
    final ObservableList<TreeItem<T>> branches = node.getChildren();
435
436
    return branches.size() == 1 && branches.get( 0 ).isLeaf();
437
  }
438
439
  /**
440
   * Inserts text that the user typed at the current caret position, then
441
   * performs an autocomplete for the variable name.
442
   *
443
   * @param text The text to insert, never null.
444
   */
445
  private void typed( final String text ) {
446
    getEditor().replaceSelection( text );
447
    vModeAutocomplete();
448
  }
449
450
  /**
451
   * Called when the user presses either End or Enter key.
452
   */
453
  private void acceptPath() {
454
    final IndexRange range = getSelectionRange();
455
456
    if( range != null ) {
457
      final int rangeEnd = range.getEnd();
458
      final StyledTextArea textArea = getEditor();
459
      textArea.deselect();
460
      textArea.moveTo( rangeEnd );
461
    }
462
  }
463
464
  /**
465
   * Replaces the entirety of the existing path (from the initial caret
466
   * position) with the given path.
467
   *
468
   * @param oldPath The path to replace.
469
   * @param newPath The replacement path.
470
   */
471
  private void replacePath( final String oldPath, final String newPath ) {
472
    final StyledTextArea textArea = getEditor();
473
    final int posBegan = getInitialCaretPosition();
474
    final int posEnded = posBegan + oldPath.length();
475
476
    textArea.deselect();
477
    textArea.replaceText( posBegan, posEnded, newPath );
478
  }
479
480
  /**
481
   * Called when the user presses the Backspace key.
482
   */
483
  private void deleteSelection() {
484
    final StyledTextArea textArea = getEditor();
485
    textArea.replaceSelection( "" );
486
    textArea.deletePreviousChar();
487
  }
488
489
  /**
490
   * Cycles the selected text through the nodes.
491
   *
492
   * @param direction true - next; false - previous
493
   */
494
  private void cycleSelection( final boolean direction ) {
495
    final TreeItem<String> node = getCurrentNode();
496
497
    // Find the sibling for the current selection and replace the current
498
    // selection with the sibling's value
499
    TreeItem< String> cycled = direction
500
      ? node.nextSibling()
501
      : node.previousSibling();
502
503
    // When cycling at the end (or beginning) of the list, jump to the first
504
    // (or last) sibling depending on the cycle direction.
505
    if( cycled == null ) {
506
      cycled = direction ? getFirstSibling( node ) : getLastSibling( node );
507
    }
508
509
    final String path = getCurrentPath();
510
    final String cycledWord = cycled.getValue();
511
    final String word = getLastPathWord();
512
    final int index = path.indexOf( word );
513
    final String cycledPath = path.substring( 0, index ) + cycledWord;
514
515
    expand( cycled );
516
    replacePath( path, cycledPath );
517
  }
518
519
  /**
520
   * Cycles to the next sibling of the currently selected tree node.
521
   */
522
  private void cyclePathNext() {
523
    cycleSelection( true );
524
  }
525
526
  /**
527
   * Cycles to the previous sibling of the currently selected tree node.
528
   */
529
  private void cyclePathPrev() {
530
    cycleSelection( false );
531
  }
532
533
  /**
534
   * Returns the variable name (or as much as has been typed so far). Returns
535
   * all the characters from the initial caret column to the the first
536
   * whitespace character. This will return a path that contains zero or more
537
   * separators.
538
   *
539
   * @return A non-null string, possibly empty.
540
   */
541
  private String getCurrentPath() {
542
    final String s = extractTextChunk();
543
    final int length = s.length();
544
545
    int i = 0;
546
547
    while( i < length && !isWhitespace( s.charAt( i ) ) ) {
548
      i++;
549
    }
550
551
    return s.substring( 0, i );
552
  }
553
554
  private <T> ObservableList<TreeItem<T>> getSiblings(
555
    final TreeItem<T> item ) {
556
    final TreeItem<T> parent = item.getParent();
557
    return parent == null ? item.getChildren() : parent.getChildren();
558
  }
559
560
  private <T> TreeItem<T> getFirstSibling( final TreeItem<T> item ) {
561
    return getFirst( getSiblings( item ), item );
562
  }
563
564
  private <T> TreeItem<T> getLastSibling( final TreeItem<T> item ) {
565
    return getLast( getSiblings( item ), item );
566
  }
567
568
  /**
569
   * Returns the caret position as an offset into the text.
570
   *
571
   * @return A value from 0 to the length of the text (minus one).
572
   */
573
  private int getCurrentCaretPosition() {
574
    return getEditor().getCaretPosition();
575
  }
576
577
  /**
578
   * Returns the caret position within the current paragraph.
579
   *
580
   * @return A value from 0 to the length of the current paragraph.
581
   */
582
  private int getCurrentCaretColumn() {
583
    return getEditor().getCaretColumn();
584
  }
585
586
  /**
587
   * Returns the last word from the path.
588
   *
589
   * @return The last token.
590
   */
591
  private String getLastPathWord() {
592
    String path = getCurrentPath();
593
594
    int i = path.indexOf( SEPARATOR );
595
596
    while( i > 0 ) {
597
      path = path.substring( i + 1 );
598
      i = path.indexOf( SEPARATOR );
599
    }
600
601
    return path;
602
  }
603
604
  /**
605
   * Returns text from the initial caret position until some arbitrarily long
606
   * number of characters. The number of characters extracted will be
607
   * getMaxVarLength, or fewer, depending on how many characters remain to be
608
   * extracted. The result from this method is trimmed to the first whitespace
609
   * character.
610
   *
611
   * @return A chunk of text that includes all the words representing a path,
612
   * and then some.
613
   */
614
  private String extractTextChunk() {
615
    final StyledTextArea textArea = getEditor();
616
    final int textBegan = getInitialCaretPosition();
617
    final int remaining = textArea.getLength() - textBegan;
618
    final int textEnded = min( remaining, getMaxVarLength() );
619
620
    return textArea.getText( textBegan, textEnded );
621
  }
622
623
  /**
624
   * Returns the node for the current path.
625
   */
626
  private TreeItem<String> getCurrentNode() {
627
    return findNode( getCurrentPath() );
628
  }
629
630
  /**
631
   * Finds the node that most closely matches the given path.
632
   *
633
   * @param path The path that represents a node.
634
   *
635
   * @return The node for the path, or the root node if the path could not be
636
   * found, but never null.
637
   */
638
  private TreeItem<String> findNode( final String path ) {
639
    return getDefinitionPane().findNode( path );
640
  }
641
642
  /**
643
   * Finds the first leaf having a value that starts with the given text.
644
   *
645
   * @param text The text to find in the definition tree.
646
   *
647
   * @return The leaf that starts with the given text, or null if not found.
648
   */
649
  private VariableTreeItem<String> findLeaf( final String text ) {
650
    return getDefinitionPane().findLeaf( text );
651
  }
652
653
  /**
654
   * Used to ignore typed keys in favour of trapping pressed keys.
655
   *
656
   * @param e The key that was typed.
657
   */
658
  private void vModeKeyTyped( KeyEvent e ) {
659
    e.consume();
660
  }
661
662
  /**
663
   * Used to lazily initialize the keyboard map.
664
   *
665
   * @return Mappings for keyTyped and keyPressed.
666
   */
667
  protected InputMap<InputEvent> createKeyboardMap() {
668
    return sequence(
669
      consume( keyTyped(), this::vModeKeyTyped ),
670
      consume( keyPressed(), this::vModeKeyPressed )
671
    );
672
  }
673
674
  private InputMap<InputEvent> getKeyboardMap() {
675
    if( this.keyboardMap == null ) {
676
      this.keyboardMap = createKeyboardMap();
677
    }
678
679
    return this.keyboardMap;
680
  }
681
682
  /**
683
   * Collapses the tree then expands and selects the given node.
684
   *
685
   * @param node The node to expand.
686
   */
687
  private void expand( final TreeItem<String> node ) {
688
    final DefinitionPane pane = getDefinitionPane();
689
    pane.collapse();
690
    pane.expand( node );
691
    pane.select( node );
692
  }
693
694
  /**
695
   * Returns true iff the key code the user typed can be used as part of a YAML
696
   * variable name.
697
   *
698
   * @param keyEvent Keyboard key press event information.
699
   *
700
   * @return true The key is a value that can be inserted into the text.
701
   */
702
  private boolean isVariableNameKey( final KeyEvent keyEvent ) {
703
    final KeyCode kc = keyEvent.getCode();
704
705
    return (kc.isLetterKey()
706
      || kc.isDigitKey()
707
      || (keyEvent.isShiftDown() && kc == MINUS))
708
      && !keyEvent.isControlDown();
709
  }
710
711
  /**
712
   * Starts to capture user input events.
713
   */
714
  private void vModeStart() {
715
    addEventListener( getKeyboardMap() );
716
  }
717
718
  /**
719
   * Restores capturing of user input events to the previous event listener.
720
   * Also asks the processing chain to modify the variable text into a
721
   * machine-readable variable based on the format required by the file type.
722
   * For example, a Markdown file (.md) will substitute a $VAR$ name while an R
723
   * file (.Rmd, .Rxml) will use `r#xVAR`.
724
   */
725
  private void vModeStop() {
726
    removeEventListener( getKeyboardMap() );
727
  }
728
729
  private VariableDecorator getVariableDecorator() {
730
    return new YamlVariableDecorator();
731
  }
732
733
  /**
734
   * Returns the index where the two strings diverge.
735
   *
736
   * @param s1 The string that could be a substring of s2, null allowed.
737
   * @param s2 The string that could be a substring of s1, null allowed.
738
   *
739
   * @return NO_DIFFERENCE if the strings are the same, otherwise the index
740
   * where they differ.
741
   */
742
  @SuppressWarnings( "StringEquality" )
743
  private int difference( final CharSequence s1, final CharSequence s2 ) {
744
    if( s1 == s2 ) {
745
      return NO_DIFFERENCE;
746
    }
747
748
    if( s1 == null || s2 == null ) {
749
      return 0;
750
    }
751
752
    int i = 0;
753
    final int limit = min( s1.length(), s2.length() );
754
755
    while( i < limit && s1.charAt( i ) == s2.charAt( i ) ) {
756
      i++;
757
    }
758
759
    // If one string was shorter than the other, that's where they differ.
760
    return i;
761
  }
762
763
  /**
764
   * Delegates to the file editor pane, and, ultimately, to its text area.
765
   */
766
  private <T extends Event, U extends T> void addEventListener(
767
    final EventPattern<? super T, ? extends U> event,
768
    final Consumer<? super U> consumer ) {
769
    getFileEditorPane().addEventListener( event, consumer );
770
  }
771
772
  /**
773
   * Delegates to the file editor pane, and, ultimately, to its text area.
774
   *
775
   * @param map The map of methods to events.
776
   */
777
  private void addEventListener( final InputMap<InputEvent> map ) {
778
    getFileEditorPane().addEventListener( map );
779
  }
780
781
  private void removeEventListener( final InputMap<InputEvent> map ) {
782
    getFileEditorPane().removeEventListener( map );
783
  }
784
785
  /**
786
   * Returns the position of the caret when variable mode editing was requested.
787
   *
788
   * @return The variable mode caret position.
789
   */
790
  private int getInitialCaretPosition() {
791
    return this.initialCaretPosition;
792
  }
793
794
  /**
795
   * Sets the position of the caret when variable mode editing was requested.
796
   * Stores the current position because only the text that comes afterwards is
797
   * a suitable variable reference.
798
   *
799
   * @return The variable mode caret position.
800
   */
801
  private void setInitialCaretPosition() {
802
    this.initialCaretPosition = getEditor().getCaretPosition();
803
  }
804
805
  private StyledTextArea getEditor() {
806
    return getFileEditorPane().getEditor();
807
  }
808
809
  public FileEditorTabPane getFileEditorPane() {
810
    return this.fileEditorPane;
811
  }
812
813
  private void setFileEditorPane( final FileEditorTabPane fileEditorPane ) {
814
    this.fileEditorPane = fileEditorPane;
815
  }
816
817
  private DefinitionPane getDefinitionPane() {
818
    return this.definitionPane;
819
  }
820
821
  private void setDefinitionPane( final DefinitionPane definitionPane ) {
822
    this.definitionPane = definitionPane;
823
  }
824
825
  private IndexRange getSelectionRange() {
826
    return getEditor().getSelection();
827
  }
828
829
  /**
830
   * Don't look ahead too far when trying to find the end of a node.
831
   *
832
   * @return 512 by default.
833
   */
834
  private int getMaxVarLength() {
835
    return getSettings().getSetting(
836
      "editor.variable.maxLength", DEFAULT_MAX_VAR_LENGTH );
837
  }
838
839
  private Settings getSettings() {
840
    return this.settings;
841
  }
842
}
1843
A src/main/java/com/scrivenvar/options/GeneralOptionsPane.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.options;
28
29
import com.scrivenvar.Messages;
30
import com.scrivenvar.ui.AbstractPane;
31
import com.scrivenvar.util.Item;
32
import java.nio.charset.Charset;
33
import java.util.ArrayList;
34
import java.util.Collection;
35
import java.util.SortedMap;
36
import javafx.scene.control.ComboBox;
37
import javafx.scene.control.Label;
38
39
/**
40
 * General options pane.
41
 *
42
 * @author Karl Tauber
43
 */
44
public class GeneralOptionsPane extends AbstractPane {
45
46
  @SuppressWarnings( "unchecked" )
47
  public GeneralOptionsPane() {
48
    initComponents();
49
50
    String defaultLineSeparator = System.getProperty( "line.separator", "\n" );
51
    String defaultLineSeparatorStr = defaultLineSeparator.replace( "\r", "CR" ).replace( "\n", "LF" );
52
    lineSeparatorField.getItems().addAll(
53
      new Item<>( Messages.get( "GeneralOptionsPane.platformDefault", defaultLineSeparatorStr ), null ),
54
      new Item<>( Messages.get( "GeneralOptionsPane.sepWindows" ), "\r\n" ),
55
      new Item<>( Messages.get( "GeneralOptionsPane.sepUnix" ), "\n" ) );
56
57
    encodingField.getItems().addAll( getAvailableEncodings() );
58
  }
59
60
  private Collection<Item<String>> getAvailableEncodings() {
61
    SortedMap<String, Charset> availableCharsets = Charset.availableCharsets();
62
63
    ArrayList<Item<String>> values = new ArrayList<>( 1 + availableCharsets.size() );
64
    values.add( new Item<>( Messages.get( "GeneralOptionsPane.platformDefault", Charset.defaultCharset().name() ), null ) );
65
66
    for( String name : availableCharsets.keySet() ) {
67
      values.add( new Item<>( name, name ) );
68
    }
69
70
    return values;
71
  }
72
73
  void load() {
74
    lineSeparatorField.setValue( new Item<>( getOptions().getLineSeparator(), getOptions().getLineSeparator() ) );
75
    encodingField.setValue( new Item<>( getOptions().getEncoding(), getOptions().getEncoding() ) );
76
77
  }
78
79
  void save() {
80
    getOptions().setLineSeparator( lineSeparatorField.getValue().value );
81
    getOptions().setEncoding( encodingField.getValue().value );
82
83
  }
84
85
  private void initComponents() {
86
    // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
87
    Label lineSeparatorLabel = new Label();
88
    lineSeparatorField = new ComboBox<>();
89
    Label lineSeparatorLabel2 = new Label();
90
    Label encodingLabel = new Label();
91
    encodingField = new ComboBox<>();
92
93
    //======== this ========
94
    setCols( "[fill][fill][fill]" );
95
    setRows( "[][]para[]" );
96
97
    //---- lineSeparatorLabel ----
98
    lineSeparatorLabel.setText( Messages.get( "GeneralOptionsPane.lineSeparatorLabel.text" ) );
99
    lineSeparatorLabel.setMnemonicParsing( true );
100
    add( lineSeparatorLabel, "cell 0 0" );
101
    add( lineSeparatorField, "cell 1 0" );
102
103
    //---- lineSeparatorLabel2 ----
104
    lineSeparatorLabel2.setText( Messages.get( "GeneralOptionsPane.lineSeparatorLabel2.text" ) );
105
    add( lineSeparatorLabel2, "cell 2 0" );
106
107
    //---- encodingLabel ----
108
    encodingLabel.setText( Messages.get( "GeneralOptionsPane.encodingLabel.text" ) );
109
    encodingLabel.setMnemonicParsing( true );
110
    add( encodingLabel, "cell 0 1" );
111
112
    //---- encodingField ----
113
    encodingField.setVisibleRowCount( 20 );
114
    add( encodingField, "cell 1 1" );
115
		// JFormDesigner - End of component initialization  //GEN-END:initComponents
116
117
    // TODO set this in JFormDesigner as soon as it supports labelFor
118
    lineSeparatorLabel.setLabelFor( lineSeparatorField );
119
    encodingLabel.setLabelFor( encodingField );
120
  }
121
122
  // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
123
  private ComboBox<Item<String>> lineSeparatorField;
124
  private ComboBox<Item<String>> encodingField;
125
	// JFormDesigner - End of variables declaration  //GEN-END:variables
126
}
1127
A src/main/java/com/scrivenvar/options/GeneralOptionsPane.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": "GeneralOptionsPane"
8
	contentType: "form/javafx"
9
	root: new FormRoot {
10
		add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) {
11
			"$layoutConstraints": ""
12
			"$columnConstraints": "[fill][fill][fill]"
13
			"$rowConstraints": "[][]para[]"
14
		} ) {
15
			name: "this"
16
			add( new FormComponent( "javafx.scene.control.Label" ) {
17
				name: "lineSeparatorLabel"
18
				"text": new FormMessage( null, "GeneralOptionsPane.lineSeparatorLabel.text" )
19
				"mnemonicParsing": true
20
				auxiliary() {
21
					"JavaCodeGenerator.variableLocal": true
22
				}
23
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
24
				"value": "cell 0 0"
25
			} )
26
			add( new FormComponent( "javafx.scene.control.ComboBox" ) {
27
				name: "lineSeparatorField"
28
				auxiliary() {
29
					"JavaCodeGenerator.typeParameters": "Item<String>"
30
				}
31
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
32
				"value": "cell 1 0"
33
			} )
34
			add( new FormComponent( "javafx.scene.control.Label" ) {
35
				name: "lineSeparatorLabel2"
36
				"text": new FormMessage( null, "GeneralOptionsPane.lineSeparatorLabel2.text" )
37
				auxiliary() {
38
					"JavaCodeGenerator.variableLocal": true
39
				}
40
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
41
				"value": "cell 2 0"
42
			} )
43
			add( new FormComponent( "javafx.scene.control.Label" ) {
44
				name: "encodingLabel"
45
				"text": new FormMessage( null, "GeneralOptionsPane.encodingLabel.text" )
46
				"mnemonicParsing": true
47
				auxiliary() {
48
					"JavaCodeGenerator.variableLocal": true
49
				}
50
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
51
				"value": "cell 0 1"
52
			} )
53
			add( new FormComponent( "javafx.scene.control.ComboBox" ) {
54
				name: "encodingField"
55
				"visibleRowCount": 20
56
				auxiliary() {
57
					"JavaCodeGenerator.typeParameters": "Item<String>"
58
				}
59
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
60
				"value": "cell 1 1"
61
			} )
62
		}, new FormLayoutConstraints( null ) {
63
			"location": new javafx.geometry.Point2D( 0.0, 0.0 )
64
			"size": new javafx.geometry.Dimension2D( 400.0, 300.0 )
65
		} )
66
	}
67
}
168
A src/main/java/com/scrivenvar/options/MarkdownOptionsPane.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": "MarkdownOptionsPane"
8
	contentType: "form/javafx"
9
	root: new FormRoot {
10
		add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) {
11
			"$rowConstraints": "[][][][][][][][][][][][][][][][][][]"
12
			"$columnConstraints": "[][fill]"
13
		} ) {
14
			name: "this"
15
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
16
				name: "smartsExtCheckBox"
17
				"text": new FormMessage( null, "MarkdownOptionsPane.smartsExtCheckBox.text" )
18
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
19
				"value": "cell 0 0"
20
			} )
21
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
22
				name: "quotesExtCheckBox"
23
				"text": new FormMessage( null, "MarkdownOptionsPane.quotesExtCheckBox.text" )
24
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
25
				"value": "cell 0 1"
26
			} )
27
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
28
				name: "abbreviationsExtCheckBox"
29
				"text": new FormMessage( null, "MarkdownOptionsPane.abbreviationsExtCheckBox.text" )
30
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
31
				"value": "cell 0 2"
32
			} )
33
			add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
34
				name: "abbreviationsExtLink"
35
				"text": new FormMessage( null, "MarkdownOptionsPane.abbreviationsExtLink.text" )
36
				"uri": "http://michelf.com/projects/php-markdown/extra/#abbr"
37
				auxiliary() {
38
					"JavaCodeGenerator.variableLocal": true
39
				}
40
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
41
				"value": "cell 0 2,gapx 0"
42
			} )
43
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
44
				name: "hardwrapsExtCheckBox"
45
				"text": new FormMessage( null, "MarkdownOptionsPane.hardwrapsExtCheckBox.text" )
46
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
47
				"value": "cell 0 3"
48
			} )
49
			add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
50
				name: "hardwrapsExtLink"
51
				"text": new FormMessage( null, "MarkdownOptionsPane.hardwrapsExtLink.text" )
52
				"uri": "https://help.github.com/articles/writing-on-github/#markup"
53
				auxiliary() {
54
					"JavaCodeGenerator.variableLocal": true
55
				}
56
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
57
				"value": "cell 0 3,gapx 0"
58
			} )
59
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
60
				name: "autolinksExtCheckBox"
61
				"text": new FormMessage( null, "MarkdownOptionsPane.autolinksExtCheckBox.text" )
62
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
63
				"value": "cell 0 4"
64
			} )
65
			add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
66
				name: "autolinksExtLink"
67
				"text": new FormMessage( null, "MarkdownOptionsPane.autolinksExtLink.text" )
68
				"uri": "https://help.github.com/articles/github-flavored-markdown/#url-autolinking"
69
				auxiliary() {
70
					"JavaCodeGenerator.variableLocal": true
71
				}
72
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
73
				"value": "cell 0 4,gapx 0"
74
			} )
75
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
76
				name: "tablesExtCheckBox"
77
				"text": new FormMessage( null, "MarkdownOptionsPane.tablesExtCheckBox.text" )
78
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
79
				"value": "cell 0 5"
80
			} )
81
			add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
82
				name: "tablesExtLink"
83
				"text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLink.text" )
84
				"uri": "http://fletcher.github.io/MultiMarkdown-4/syntax.html#tables"
85
				auxiliary() {
86
					"JavaCodeGenerator.variableLocal": true
87
				}
88
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
89
				"value": "cell 0 5,gapx 0"
90
			} )
91
			add( new FormComponent( "javafx.scene.control.Label" ) {
92
				name: "tablesExtLabel"
93
				"text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLabel.text" )
94
				auxiliary() {
95
					"JavaCodeGenerator.variableLocal": true
96
				}
97
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
98
				"value": "cell 0 5,gapx 3"
99
			} )
100
			add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
101
				name: "tablesExtLink2"
102
				"text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLink2.text" )
103
				"uri": "https://michelf.ca/projects/php-markdown/extra/#table"
104
				auxiliary() {
105
					"JavaCodeGenerator.variableLocal": true
106
				}
107
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
108
				"value": "cell 0 5,gapx 3 3"
109
			} )
110
			add( new FormComponent( "javafx.scene.control.Label" ) {
111
				name: "tablesExtLabel2"
112
				"text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLabel2.text" )
113
				auxiliary() {
114
					"JavaCodeGenerator.variableLocal": true
115
				}
116
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
117
				"value": "cell 0 5,gapx 0"
118
			} )
119
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
120
				name: "definitionListsExtCheckBox"
121
				"text": new FormMessage( null, "MarkdownOptionsPane.definitionListsExtCheckBox.text" )
122
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
123
				"value": "cell 0 6"
124
			} )
125
			add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
126
				name: "definitionListsExtLink"
127
				"text": new FormMessage( null, "MarkdownOptionsPane.definitionListsExtLink.text" )
128
				"uri": "https://michelf.ca/projects/php-markdown/extra/#def-list"
129
				auxiliary() {
130
					"JavaCodeGenerator.variableLocal": true
131
				}
132
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
133
				"value": "cell 0 6,gapx 0"
134
			} )
135
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
136
				name: "fencedCodeBlocksExtCheckBox"
137
				"text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtCheckBox.text" )
138
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
139
				"value": "cell 0 7"
140
			} )
141
			add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
142
				name: "fencedCodeBlocksExtLink"
143
				"text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLink.text" )
144
				"uri": "http://michelf.com/projects/php-markdown/extra/#fenced-code-blocks"
145
				auxiliary() {
146
					"JavaCodeGenerator.variableLocal": true
147
				}
148
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
149
				"value": "cell 0 7,gapx 0"
150
			} )
151
			add( new FormComponent( "javafx.scene.control.Label" ) {
152
				name: "fencedCodeBlocksExtLabel"
153
				"text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLabel.text" )
154
				auxiliary() {
155
					"JavaCodeGenerator.variableLocal": true
156
				}
157
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
158
				"value": "cell 0 7,gapx 3"
159
			} )
160
			add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
161
				name: "fencedCodeBlocksExtLink2"
162
				"text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLink2.text" )
163
				"uri": "https://help.github.com/articles/github-flavored-markdown/#fenced-code-blocks"
164
				auxiliary() {
165
					"JavaCodeGenerator.variableLocal": true
166
				}
167
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
168
				"value": "cell 0 7,gapx 3"
169
			} )
170
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
171
				name: "wikilinksExtCheckBox"
172
				"text": new FormMessage( null, "MarkdownOptionsPane.wikilinksExtCheckBox.text" )
173
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
174
				"value": "cell 0 8"
175
			} )
176
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
177
				name: "strikethroughExtCheckBox"
178
				"text": new FormMessage( null, "MarkdownOptionsPane.strikethroughExtCheckBox.text" )
179
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
180
				"value": "cell 0 9"
181
			} )
182
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
183
				name: "anchorlinksExtCheckBox"
184
				"text": new FormMessage( null, "MarkdownOptionsPane.anchorlinksExtCheckBox.text" )
185
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
186
				"value": "cell 0 10"
187
			} )
188
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
189
				name: "suppressHtmlBlocksExtCheckBox"
190
				"text": new FormMessage( null, "MarkdownOptionsPane.suppressHtmlBlocksExtCheckBox.text" )
191
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
192
				"value": "cell 0 11"
193
			} )
194
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
195
				name: "suppressInlineHtmlExtCheckBox"
196
				"text": new FormMessage( null, "MarkdownOptionsPane.suppressInlineHtmlExtCheckBox.text" )
197
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
198
				"value": "cell 0 12"
199
			} )
200
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
201
				name: "atxHeaderSpaceExtCheckBox"
202
				"text": new FormMessage( null, "MarkdownOptionsPane.atxHeaderSpaceExtCheckBox.text" )
203
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
204
				"value": "cell 0 13"
205
			} )
206
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
207
				name: "forceListItemParaExtCheckBox"
208
				"text": new FormMessage( null, "MarkdownOptionsPane.forceListItemParaExtCheckBox.text" )
209
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
210
				"value": "cell 0 14"
211
			} )
212
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
213
				name: "relaxedHrRulesExtCheckBox"
214
				"text": new FormMessage( null, "MarkdownOptionsPane.relaxedHrRulesExtCheckBox.text" )
215
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
216
				"value": "cell 0 15"
217
			} )
218
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
219
				name: "taskListItemsExtCheckBox"
220
				"text": new FormMessage( null, "MarkdownOptionsPane.taskListItemsExtCheckBox.text" )
221
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
222
				"value": "cell 0 16"
223
			} )
224
			add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
225
				name: "extAnchorLinksExtCheckBox"
226
				"text": new FormMessage( null, "MarkdownOptionsPane.extAnchorLinksExtCheckBox.text" )
227
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
228
				"value": "cell 0 17"
229
			} )
230
		}, new FormLayoutConstraints( null ) {
231
			"location": new javafx.geometry.Point2D( 0.0, 0.0 )
232
			"size": new javafx.geometry.Dimension2D( 600.0, 531.0 )
233
		} )
234
	}
235
}
1236
A src/main/java/com/scrivenvar/options/OptionsDialog.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.options;
29
30
import com.scrivenvar.Messages;
31
import com.scrivenvar.Services;
32
import com.scrivenvar.service.Options;
33
import com.scrivenvar.service.events.impl.ButtonOrderPane;
34
import java.util.prefs.Preferences;
35
import javafx.event.ActionEvent;
36
import javafx.scene.control.ButtonType;
37
import javafx.scene.control.Dialog;
38
import javafx.scene.control.DialogPane;
39
import javafx.scene.control.Tab;
40
import javafx.scene.control.TabPane;
41
import javafx.stage.Window;
42
43
/**
44
 * Options dialog.
45
 *
46
 * @author Karl Tauber and White Magic Software, Ltd.
47
 */
48
public class OptionsDialog extends Dialog<Void> {
49
50
  private final Options options = Services.load( Options.class );
51
52
  public OptionsDialog( Window owner ) {
53
    setTitle( Messages.get( "OptionsDialog.title" ) );
54
    initOwner( owner );
55
56
    initComponents();
57
58
    tabPane.getStyleClass().add( TabPane.STYLE_CLASS_FLOATING );
59
60
    setDialogPane( new ButtonOrderPane() );
61
62
    final DialogPane dialogPane = getDialogPane();
63
    dialogPane.setContent( tabPane );
64
    dialogPane.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL );
65
66
    dialogPane.lookupButton( ButtonType.OK ).addEventHandler( ActionEvent.ACTION, e -> {
67
      save();
68
      e.consume();
69
    } );
70
71
    // load options
72
    load();
73
74
    // select last tab
75
    int tabIndex = getState().getInt( "lastOptionsTab", -1 );
76
    if( tabIndex > 0 ) {
77
      tabPane.getSelectionModel().select( tabIndex );
78
    }
79
80
    // remember last selected tab
81
    setOnHidden( e -> {
82
      getState().putInt( "lastOptionsTab", tabPane.getSelectionModel().getSelectedIndex() );
83
    } );
84
  }
85
86
  private Options getOptions() {
87
    return options;
88
  }
89
  
90
  private Preferences getState() {
91
    return getOptions().getState();
92
  }
93
94
  private void load() {
95
    generalOptionsPane.load();
96
  }
97
98
  private void save() {
99
    generalOptionsPane.save();
100
    Services.load( Options.class ).save();
101
  }
102
103
  private void initComponents() {
104
    // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
105
    tabPane = new TabPane();
106
    generalTab = new Tab();
107
    generalOptionsPane = new GeneralOptionsPane();
108
109
    //======== tabPane ========
110
    {
111
      tabPane.setTabClosingPolicy( TabPane.TabClosingPolicy.UNAVAILABLE );
112
113
      //======== generalTab ========
114
      {
115
        generalTab.setText( Messages.get( "OptionsDialog.generalTab.text" ) );
116
        generalTab.setContent( generalOptionsPane );
117
      }
118
119
      tabPane.getTabs().addAll( generalTab );
120
    }
121
    // JFormDesigner - End of component initialization  //GEN-END:initComponents
122
  }
123
124
  // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
125
  private TabPane tabPane;
126
  private Tab generalTab;
127
  private GeneralOptionsPane generalOptionsPane;
128
	// JFormDesigner - End of variables declaration  //GEN-END:variables
129
}
1130
A src/main/java/com/scrivenvar/options/OptionsDialog.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.keyPrefix": "OptionsDialog"
7
	"i18n.autoExternalize": true
8
	contentType: "form/javafx"
9
	root: new FormRoot {
10
		add( new FormContainer( "javafx.scene.control.TabPane", new FormLayoutManager( class javafx.scene.control.TabPane ) ) {
11
			name: "tabPane"
12
			"tabClosingPolicy": enum javafx.scene.control.TabPane$TabClosingPolicy UNAVAILABLE
13
			add( new FormContainer( "javafx.scene.control.Tab", new FormLayoutManager( class javafx.scene.control.Tab ) ) {
14
				name: "generalTab"
15
				"text": new FormMessage( null, "OptionsDialog.generalTab.text" )
16
				add( new FormComponent( "com.scrivendor.options.GeneralOptionsPane" ) {
17
					name: "generalOptionsPane"
18
				} )
19
			} )
20
			add( new FormContainer( "javafx.scene.control.Tab", new FormLayoutManager( class javafx.scene.control.Tab ) ) {
21
				name: "markdownTab"
22
				"text": new FormMessage( null, "OptionsDialog.markdownTab.text" )
23
				add( new FormComponent( "com.scrivendor.options.MarkdownOptionsPane" ) {
24
					name: "markdownOptionsPane"
25
				} )
26
			} )
27
		}, new FormLayoutConstraints( null ) {
28
			"location": new javafx.geometry.Point2D( 0.0, 0.0 )
29
			"size": new javafx.geometry.Dimension2D( 600.0, 560.0 )
30
		} )
31
	}
32
}
133
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
   * Calls the superclass to construct the instance.
39
   *
40
   * @param comparate Not null.
41
   */
42
  public StartsPredicate( final String comparate ) {
43
    super( comparate );
44
  }
45
46
  /**
47
   * Compares two strings.
48
   *
49
   * @param comparator A non-null string, possibly empty.
50
   *
51
   * @return true The strings are equal, ignoring case.
52
   */
53
  @Override
54
  public boolean test( final String comparator ) {
55
    final String comparate = getComparate().toLowerCase();
56
    return comparator.startsWith( comparate.toLowerCase() );
57
  }
58
}
159
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;
31
import java.nio.file.Path;
32
import javafx.beans.value.ObservableValue;
33
import javafx.concurrent.Worker.State;
34
import static javafx.concurrent.Worker.State.SUCCEEDED;
35
import javafx.scene.layout.Pane;
36
import javafx.scene.web.WebEngine;
37
import javafx.scene.web.WebView;
38
39
/**
40
 * HTML preview pane is responsible for rendering an HTML document.
41
 *
42
 * @author Karl Tauber and White Magic Software, Ltd.
43
 */
44
public final class HTMLPreviewPane extends Pane {
45
46
  private final WebView webView = new WebView();
47
  private String html;
48
  private Path path;
49
50
  /**
51
   * Creates a new preview pane that can scroll to the caret position within the
52
   * document.
53
   *
54
   * @param path The base path for loading resources, such as images.
55
   */
56
  public HTMLPreviewPane( final Path path ) {
57
    setPath( path );
58
    initListeners();
59
60
    // Prevent tabbing into the preview pane.
61
    getWebView().setFocusTraversable( false );
62
  }
63
64
  /**
65
   * Updates the internal HTML source, loads it into the preview pane, then
66
   * scrolls to the caret position.
67
   *
68
   * @param html
69
   */
70
  public void update( final String html ) {
71
    setHtml( html );
72
    update();
73
  }
74
75
  private void update() {
76
    getEngine().loadContent(
77
      "<!DOCTYPE html>"
78
      + "<html>"
79
      + "<head>"
80
      + "<link rel='stylesheet' href='" + getClass().getResource( "webview.css" ) + "'>"
81
      + getBase()
82
      + "</head>"
83
      + "<body>"
84
      + getHtml()
85
      + "</body>"
86
      + "</html>" );
87
  }
88
89
  private String getBase() {
90
    final Path basePath = getPath();
91
92
    return basePath == null
93
      ? ""
94
      : ("<base href='" + basePath.getParent().toUri().toString() + "'>");
95
  }
96
97
  /**
98
   * Initializes observers for document changes. When the document is reloaded
99
   * with new HTML, this triggers a scroll event that repositions the document
100
   * to the injected caret (that corresponds with the position in the text
101
   * editor).
102
   */
103
  private void initListeners() {
104
    // Scrolls to the caret after the content has been loaded.
105
    getEngine().getLoadWorker().stateProperty().addListener(
106
      (ObservableValue<? extends State> observable,
107
        State oldValue, State newValue) -> {
108
        if( newValue == SUCCEEDED ) {
109
          scrollToCaret();
110
        }
111
      } );
112
  }
113
114
  /**
115
   * Scrolls to the caret position in the document.
116
   */
117
  private void scrollToCaret() {
118
    execute( getScrollScript() );
119
  }
120
121
  /**
122
   * Returns the JavaScript used to scroll the WebView pane.
123
   *
124
   * @return A script that tries to center the view port on the CARET POSITION.
125
   */
126
  private String getScrollScript() {
127
    return ""
128
      + "var e = document.getElementById('" + CARET_POSITION + "');"
129
      + "if( e != null ) { "
130
      + "  Element.prototype.topOffset = function () {"
131
      + "    return this.offsetTop + (this.offsetParent ? this.offsetParent.topOffset() : 0);"
132
      + "  };"
133
      + "  window.scrollTo( 0, e.topOffset() - (window.innerHeight / 2 ) );"
134
      + "}";
135
  }
136
137
  private Object execute( final String script ) {
138
    return getEngine().executeScript( script );
139
  }
140
141
  private WebEngine getEngine() {
142
    return getWebView().getEngine();
143
  }
144
145
  public WebView getWebView() {
146
    return this.webView;
147
  }
148
149
  private String getHtml() {
150
    return this.html;
151
  }
152
153
  private void setHtml( final String html ) {
154
    this.html = html;
155
  }
156
157
  private Path getPath() {
158
    return this.path;
159
  }
160
161
  private void setPath( final Path path ) {
162
    this.path = path;
163
  }
164
}
1165
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.MD_CARET_POSITION;
31
import static java.lang.Character.isLetter;
32
import org.fxmisc.richtext.model.TextEditingArea;
33
34
/**
35
 * Responsible for inserting the magic CARET POSITION into the markdown so
36
 * that, upon rendering into HTML, the HTML pane can scroll to the correct
37
 * position (relative to the caret position in the editor).
38
 *
39
 * @author White Magic Software, Ltd.
40
 */
41
public class MarkdownCaretInsertionProcessor extends AbstractProcessor<String> {
42
43
  private TextEditingArea editor;
44
45
  /**
46
   * Constructs a processor capable of inserting a caret marker into Markdown.
47
   *
48
   * @param processor The next processor in the chain.
49
   * @param editor The editor that has a caret with a position in the text.
50
   */
51
  public MarkdownCaretInsertionProcessor(
52
    final Processor<String> processor, final TextEditingArea editor ) {
53
    super( processor );
54
    setEditor( editor );
55
  }
56
57
  /**
58
   * Changes the text to insert a "caret" at the caret position. This will
59
   * insert the unique key of Constants.MD_CARET_POSITION into the document.
60
   *
61
   * @param t The document text to process.
62
   *
63
   * @return The document text with the Markdown caret text inserted at the
64
   * caret position (given at construction time).
65
   */
66
  @Override
67
  public String processLink( final String t ) {
68
    int offset = getCaretPosition();
69
    final int length = t.length();
70
71
    // Insert the caret at the closest non-Markdown delimiter (i.e., the 
72
    // closest character from the caret position forward).
73
    while( offset < length && !isLetter( t.charAt( offset ) ) ) {
74
      offset++;
75
    }
76
77
    // Insert the caret position into the Markdown text, but don't interfere
78
    // with the Markdown iteself.
79
    return new StringBuilder( t ).replace(
80
      offset, offset, MD_CARET_POSITION ).toString();
81
  }
82
83
  /**
84
   * Returns the editor's caret position.
85
   *
86
   * @return Where the user has positioned the caret.
87
   */
88
  private int getCaretPosition() {
89
    return getEditor().getCaretPosition();
90
  }
91
92
  /**
93
   * Returns the editor that has a caret position.
94
   *
95
   * @return An editor with a caret position.
96
   */
97
  private TextEditingArea getEditor() {
98
    return this.editor;
99
  }
100
101
  private void setEditor( final TextEditingArea editor ) {
102
    this.editor = editor;
103
  }
104
}
1105
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;
31
import static com.scrivenvar.Constants.MD_CARET_POSITION;
32
33
/**
34
 * Responsible for replacing the caret position marker with an HTML element
35
 * suitable to use as a reference for scrolling a view port.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public class MarkdownCaretReplacementProcessor extends AbstractProcessor<String> {
40
  private static final int INDEX_NOT_FOUND = -1;
41
42
  private static final String HTML_ELEMENT
43
    = "<span id='" + CARET_POSITION + "'></span>";
44
45
  public MarkdownCaretReplacementProcessor( final Processor<String> processor ) {
46
    super( processor );
47
  }
48
49
  /**
50
   * Replaces each MD_CARET_POSITION with an HTML element that has an id
51
   * attribute of CARET_POSITION. This should only replace one item.
52
   *
53
   * @param t The text that contains
54
   *
55
   * @return
56
   */
57
  @Override
58
  public String processLink( final String t ) {
59
    return replace( t, MD_CARET_POSITION, HTML_ELEMENT );
60
  }
61
62
  /**
63
   * Replaces the needle with thread in the given haystack. Based on Apache
64
   * Commons 3 StringUtils.replace method. Should be faster than
65
   * String.replace, which performs a little regex under the hood.
66
   *
67
   * @param haystack Search this string for the needle, must not be null.
68
   * @param needle The text to find in the haystack.
69
   * @param thread Replace the needle with this text, if the needle is found.
70
   *
71
   * @return The haystack with the first instance of needle replaced with
72
   * thread.
73
   */
74
  private static String replace(
75
    final String haystack, final String needle, final String thread ) {
76
77
    final int end = haystack.indexOf( needle, 0 );
78
79
    if( end == INDEX_NOT_FOUND ) {
80
      return haystack;
81
    }
82
83
    int start = 0;
84
    final int needleLength = needle.length();
85
86
    int increase = thread.length() - needleLength;
87
    increase = (increase < 0 ? 0 : increase);
88
    final StringBuilder buffer = new StringBuilder( haystack.length() + increase );
89
90
    if( end != INDEX_NOT_FOUND ) {
91
      buffer.append( haystack.substring( start, end ) ).append( thread );
92
      start = end + needleLength;
93
    }
94
95
    return buffer.append( haystack.substring( start ) ).toString();
96
  }
97
}
198
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
   * @return The value after having been processed by each link.
45
   */
46
  public void processChain( T t );
47
48
  /**
49
   * Processes the given content providing a transformation from one document
50
   * format into another. For example, this could convert from XML to text using
51
   * an XSLT processor, or from markdown to HTML.
52
   *
53
   * @param t The type of object to process.
54
   *
55
   * @return The post-processed document, or null if processing should stop.
56
   */
57
  public T processLink( T t );
58
59
  /**
60
   * Adds a document processor to call after this processor finishes processing
61
   * the document given to the process method.
62
   *
63
   * @return The processor that should transform the document after this
64
   * instance has finished processing.
65
   */
66
  public Processor<T> next();
67
}
168
A src/main/java/com/scrivenvar/processors/TextChangeProcessor.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 javafx.beans.value.ChangeListener;
31
import javafx.beans.value.ObservableValue;
32
33
/**
34
 * Responsible for forwarding change events to the document process chain. This
35
 * class isolates knowledge of the change events from the other processors.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public class TextChangeProcessor extends AbstractProcessor<String>
40
  implements ChangeListener<String> {
41
42
  /**
43
   * Constructs a new text processor that listens for changes to text and then
44
   * injects them into the processing chain.
45
   *
46
   * @param successor Usually the HTML Preview Processor.
47
   */
48
  public TextChangeProcessor( final Processor<String> successor ) {
49
    super( successor );
50
  }
51
52
  /**
53
   * Called when the text editor changes.
54
   *
55
   * @param observable Unused.
56
   * @param oldValue The value before being changed (unused).
57
   * @param newValue The value after being changed (passed to processChain).
58
   */
59
  @Override
60
  public void changed(
61
    final ObservableValue<? extends String> observable,
62
    final String oldValue,
63
    final String newValue ) {
64
    processChain( newValue );
65
  }
66
67
  /**
68
   * Performs no processing.
69
   *
70
   * @param t Returned value.
71
   *
72
   * @return t, without any processing.
73
   */
74
  @Override
75
  public String processLink( String t ) {
76
    return t;
77
  }
78
}
179
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.lang.StringUtils.replaceEach;
32
33
/**
34
 * Replaces text using Apache's StringUtils.replaceEach method.
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public class StringUtilsReplacer extends AbstractTextReplacer {
39
40
  /**
41
   * Default (empty) constructor.
42
   */
43
  protected StringUtilsReplacer() {
44
  }
45
46
  @Override
47
  public String replace( final String text, final Map<String, String> map ) {
48
    return replaceEach( text, keys( map ), values( map ) );
49
  }
50
}
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
import javafx.beans.property.StringProperty;
32
33
/**
34
 * Options
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public interface Options {
39
  
40
  public Preferences getState();
41
42
  public void load( Preferences options );
43
44
  public void save();
45
46
  public String getLineSeparator();
47
48
  public void setLineSeparator( String lineSeparator );
49
50
  public StringProperty lineSeparatorProperty();
51
52
  public String getEncoding();
53
54
  public void setEncoding( String encoding );
55
56
  public StringProperty encodingProperty();
57
}
158
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.List;
31
32
/**
33
 * Defines how settings and options can be retrieved.
34
 * 
35
 * @author White Magic Software, Ltd.
36
 */
37
public interface Settings extends Service {
38
39
  /**
40
   * Returns a setting property or its default value.
41
   *
42
   * @param property The property key name to obtain its value.
43
   * @param defaultValue The default value to return iff the property cannot
44
   * be found.
45
   *
46
   * @return The property value for the given property key.
47
   */
48
  public String getSetting( String property, String defaultValue );
49
  
50
  /**
51
   * Returns a setting property or its default value.
52
   *
53
   * @param property The property key name to obtain its value.
54
   * @param defaultValue The default value to return iff the property cannot
55
   * be found.
56
   *
57
   * @return The property value for the given property key.
58
   */
59
  public int getSetting( String property, int defaultValue );
60
61
  /**
62
   * Returns a setting property or its default value.
63
   *
64
   * @param property The property key name to obtain its value.
65
   * @param defaults The default values to return iff the property cannot
66
   * be found.
67
   *
68
   * @return The property values for the given property key.
69
   */
70
  public List<Object> getSettingList( String property, List<String> defaults );
71
72
73
  /**
74
   * Convert the generic list of property objects into strings.
75
   *
76
   * @param property The property value to coerce.
77
   * @param defaults The defaults values to use should the property be unset.
78
   *
79
   * @return The list of properties coerced from objects to strings.
80
   */
81
  public List<String> getStringSettingList( String property, List<String> defaults );
82
}
183
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 com.scrivenvar.service.Options;
30
import static com.scrivenvar.util.Utils.putPrefs;
31
import java.util.prefs.Preferences;
32
import static java.util.prefs.Preferences.userRoot;
33
import javafx.beans.property.SimpleStringProperty;
34
import javafx.beans.property.StringProperty;
35
36
/**
37
 * Persistent options user can change at runtime.
38
 *
39
 * @author Karl Tauber and White Magic Software, Ltd.
40
 */
41
public class DefaultOptions implements Options {
42
  private final StringProperty LINE_SEPARATOR = new SimpleStringProperty();
43
  private final StringProperty ENCODING = new SimpleStringProperty();
44
45
  private Preferences preferences;
46
  
47
  public DefaultOptions() {
48
    setPreferences( getRootPreferences().node( "options" ) );
49
  }
50
  
51
  private void setPreferences( Preferences preferences ) {
52
    this.preferences = preferences;
53
  }
54
55
  private Preferences getRootPreferences() {
56
    return userRoot().node( "application" );
57
  }
58
59
  @Override
60
  public Preferences getState() {
61
    return getRootPreferences().node( "state" );
62
  }
63
64
  public Preferences getPreferences() {
65
    return this.preferences;
66
  }
67
68
  @Override
69
  public void load( Preferences options ) {
70
    setLineSeparator( options.get( "lineSeparator", null ) );
71
    setEncoding( options.get( "encoding", null ) );
72
  }
73
74
  @Override
75
  public void save() {
76
    final Preferences prefs = getPreferences();
77
    
78
    putPrefs( prefs, "lineSeparator", getLineSeparator(), null );
79
    putPrefs( prefs, "encoding", getEncoding(), null );
80
  }
81
82
  @Override
83
  public String getLineSeparator() {
84
    return LINE_SEPARATOR.get();
85
  }
86
87
  @Override
88
  public void setLineSeparator( String lineSeparator ) {
89
    LINE_SEPARATOR.set( lineSeparator );
90
  }
91
92
  @Override
93
  public StringProperty lineSeparatorProperty() {
94
    return LINE_SEPARATOR;
95
  }
96
97
  @Override
98
  public String getEncoding() {
99
    return ENCODING.get();
100
  }
101
102
  @Override
103
  public void setEncoding( String encoding ) {
104
    ENCODING.set( encoding );
105
  }
106
107
  @Override
108
  public StringProperty encodingProperty() {
109
    return ENCODING;
110
  }
111
}
1112
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.net.URISyntaxException;
34
import java.net.URL;
35
import java.util.ArrayList;
36
import java.util.List;
37
import java.util.Objects;
38
import java.util.stream.Collectors;
39
import org.apache.commons.configuration.ConfigurationException;
40
import org.apache.commons.configuration.PropertiesConfiguration;
41
42
/**
43
 * Responsible for loading settings that help avoid hard-coded assumptions.
44
 *
45
 * @author White Magic Software, Ltd.
46
 */
47
public class DefaultSettings implements Settings {
48
49
  private PropertiesConfiguration properties;
50
51
  public DefaultSettings()
52
    throws ConfigurationException, URISyntaxException, IOException {
53
    setProperties(createProperties());
54
  }
55
56
  /**
57
   * Returns the value of a string property.
58
   *
59
   * @param property The property key.
60
   * @param defaultValue The value to return if no property key has been set.
61
   *
62
   * @return The property key value, or defaultValue when no key found.
63
   */
64
  @Override
65
  public String getSetting(final String property, final String defaultValue) {
66
    return getSettings().getString(property, defaultValue);
67
  }
68
69
  /**
70
   * Returns the value of a string property.
71
   *
72
   * @param property The property key.
73
   * @param defaultValue The value to return if no property key has been set.
74
   *
75
   * @return The property key value, or defaultValue when no key found.
76
   */
77
  @Override
78
  public int getSetting(final String property, final int defaultValue) {
79
    return getSettings().getInt(property, defaultValue);
80
  }
81
82
  @Override
83
  public List<Object> getSettingList(final String property, List<String> defaults) {
84
    if (defaults == null) {
85
      defaults = new ArrayList<>();
86
    }
87
    
88
    return getSettings().getList(property, defaults);
89
  }
90
91
  /**
92
   * Convert the generic list of property objects into strings.
93
   *
94
   * @param property The property value to coerce.
95
   * @param defaults The defaults values to use should the property be unset.
96
   *
97
   * @return The list of properties coerced from objects to strings.
98
   */
99
  @Override
100
  public List<String> getStringSettingList(
101
    final String property, final List<String> defaults) {
102
    final List<Object> settings = getSettingList(property, defaults);
103
104
    return settings.stream()
105
      .map(object -> Objects.toString(object, null))
106
      .collect(Collectors.toList());
107
  }
108
109
  private PropertiesConfiguration createProperties()
110
    throws ConfigurationException {
111
    final URL url = getPropertySource();
112
113
    return url == null
114
      ? new PropertiesConfiguration()
115
      : new PropertiesConfiguration(url);
116
  }
117
118
  private URL getPropertySource() {
119
    return getClass().getResource(getSettingsFilename());
120
  }
121
122
  private String getSettingsFilename() {
123
    return SETTINGS_NAME;
124
  }
125
126
  private void setProperties(final PropertiesConfiguration configuration) {
127
    this.properties = configuration;
128
  }
129
130
  private PropertiesConfiguration getSettings() {
131
    return this.properties;
132
  }
133
}
1134
A src/main/java/com/scrivenvar/ui/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.ui;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.Options;
32
import java.util.prefs.Preferences;
33
import org.tbee.javafx.scene.layout.fxml.MigPane;
34
35
/**
36
 * Provides options to all subclasses.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public abstract class AbstractPane extends MigPane {
41
42
  private final Options options = Services.load( Options.class );
43
44
  protected Options getOptions() {
45
    return this.options;
46
  }
47
  
48
  protected Preferences getState() {
49
    return getOptions().getState();
50
  }
51
}
152
A src/main/java/com/scrivenvar/ui/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.ui;
29
30
import static com.scrivenvar.Constants.SEPARATOR;
31
import com.scrivenvar.decorators.YamlVariableDecorator;
32
import com.scrivenvar.decorators.VariableDecorator;
33
import static com.scrivenvar.editor.VariableNameInjector.DEFAULT_MAX_VAR_LENGTH;
34
import java.util.HashMap;
35
import java.util.Map;
36
import java.util.Stack;
37
import javafx.scene.control.TreeItem;
38
39
/**
40
 * Provides behaviour afforded to variable names and their corresponding value.
41
 *
42
 * @author White Magic Software, Ltd.
43
 * @param <T> The type of TreeItem (usually String).
44
 */
45
public class VariableTreeItem<T> extends TreeItem<T> {
46
47
  private final static int DEFAULT_MAP_SIZE = 1000;
48
  
49
  private final static VariableDecorator VARIABLE_DECORATOR =
50
    new YamlVariableDecorator();
51
52
  /**
53
   * Flattened tree.
54
   */
55
  private Map<String, String> map;
56
57
  /**
58
   * Constructs a new item with a default value.
59
   *
60
   * @param value Passed up to superclass.
61
   */
62
  public VariableTreeItem( final T value ) {
63
    super( value );
64
  }
65
66
  /**
67
   * Finds a leaf starting at the current node with text that matches the given
68
   * value.
69
   *
70
   * @param text The text to match against each leaf in the tree.
71
   *
72
   * @return The leaf that has a value starting with the given text.
73
   */
74
  public VariableTreeItem<T> findLeaf( final String text ) {
75
    final Stack<VariableTreeItem<T>> stack = new Stack<>();
76
    final VariableTreeItem<T> root = this;
77
78
    stack.push( root );
79
80
    boolean found = false;
81
    VariableTreeItem<T> node = null;
82
83
    while( !found && !stack.isEmpty() ) {
84
      node = stack.pop();
85
86
      if( node.valueStartsWith( text ) ) {
87
        found = true;
88
      } else {
89
        for( final TreeItem<T> child : node.getChildren() ) {
90
          stack.push( (VariableTreeItem<T>)child );
91
        }
92
93
        // No match found, yet.
94
        node = null;
95
      }
96
    }
97
98
    return (VariableTreeItem<T>)node;
99
  }
100
101
  /**
102
   * Returns true if this node is a leaf and its value starts with the given
103
   * text.
104
   *
105
   * @param s The text to compare against the node value.
106
   *
107
   * @return true Node is a leaf and its value starts with the given value.
108
   */
109
  private boolean valueStartsWith( final String s ) {
110
    return isLeaf() && getValue().toString().startsWith( s );
111
  }
112
113
  /**
114
   * Returns the path for this node, with nodes made distinct using the
115
   * separator character. This uses two loops: one for pushing nodes onto a
116
   * stack and one for popping them off to create the path in desired order.
117
   *
118
   * @return A non-null string, possibly empty.
119
   */
120
  public String toPath() {
121
    final Stack<TreeItem<T>> stack = new Stack<>();
122
    TreeItem<T> node = this;
123
124
    while( node.getParent() != null ) {
125
      stack.push( node );
126
      node = node.getParent();
127
    }
128
129
    final StringBuilder sb = new StringBuilder( DEFAULT_MAX_VAR_LENGTH );
130
131
    while( !stack.isEmpty() ) {
132
      node = stack.pop();
133
134
      if( !node.isLeaf() ) {
135
        sb.append( node.getValue() );
136
137
        // This will add a superfluous separator, but instead of peeking at
138
        // the stack all the time, the last separator will be removed outside
139
        // the loop (one operation executed once).
140
        sb.append( SEPARATOR );
141
      }
142
    }
143
144
    // Remove the trailing SEPARATOR.
145
    if( sb.length() > 0 ) {
146
      sb.setLength( sb.length() - 1 );
147
    }
148
149
    return sb.toString();
150
  }
151
152
  /**
153
   * Returns the hierarchy, flattened to key-value pairs.
154
   *
155
   * @return A map of this tree's key-value pairs.
156
   */
157
  public Map<String, String> getMap() {
158
    if( this.map == null ) {
159
      this.map = new HashMap<>( DEFAULT_MAP_SIZE );
160
      populate( this, this.map );
161
    }
162
163
    return this.map;
164
  }
165
166
  private void populate( final TreeItem<T> parent, final Map<String, String> map ) {
167
    for( final TreeItem<T> child : parent.getChildren() ) {
168
      if( child.isLeaf() ) {
169
        @SuppressWarnings( "unchecked" )
170
        final String key = toVariable( ((VariableTreeItem<String>)child).toPath() );
171
        final String value = child.getValue().toString();
172
173
        map.put( key, value );
174
      } else {
175
        populate( child, map );
176
      }
177
    }
178
  }
179
180
  /**
181
   * Converts the name of the key to a simple variable by enclosing it with
182
   * dollar symbols.
183
   *
184
   * @param key The key name to change to a variable.
185
   *
186
   * @return $key$
187
   */
188
  public String toVariable( final String key ) {
189
    return VARIABLE_DECORATOR.decorate( key );
190
  }
191
}
1192
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
28
package com.scrivenvar.util;
29
30
/**
31
 * Simple item for a ChoiceBox, ComboBox or ListView.
32
 * Consists of a string name and a value object.
33
 * toString() returns the name.
34
 * equals() compares the value and hashCode() returns the hash code of the value.
35
 *
36
 * @author Karl Tauber
37
 */
38
public class Item<V>
39
{
40
	public final String name;
41
	public final V value;
42
43
	public Item(String name, V value) {
44
		this.name = name;
45
		this.value = value;
46
	}
47
48
	@Override
49
	public boolean equals(Object obj) {
50
		if (this == obj)
51
			return true;
52
		if (!(obj instanceof Item))
53
			return false;
54
		return Utils.safeEquals(value, ((Item<?>)obj).value);
55
	}
56
57
	@Override
58
	public int hashCode() {
59
		return (value != null) ? value.hashCode() : 0;
60
	}
61
62
	@Override
63
	public String toString() {
64
		return name;
65
	}
66
}
167
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
45
  private final Stage stage;
46
  private final Preferences state;
47
48
  private Rectangle normalBounds;
49
  private boolean runLaterPending;
50
51
  public StageState( Stage stage, Preferences state ) {
52
    this.stage = stage;
53
    this.state = state;
54
55
    restore();
56
57
    stage.addEventHandler( WindowEvent.WINDOW_HIDING, e -> save() );
58
59
    stage.xProperty().addListener( (ob, o, n) -> boundsChanged() );
60
    stage.yProperty().addListener( (ob, o, n) -> boundsChanged() );
61
    stage.widthProperty().addListener( (ob, o, n) -> boundsChanged() );
62
    stage.heightProperty().addListener( (ob, o, n) -> boundsChanged() );
63
  }
64
65
  private void save() {
66
    Rectangle bounds = isNormalState() ? getStageBounds() : normalBounds;
67
    if( bounds != null ) {
68
      state.putDouble( "windowX", bounds.getX() );
69
      state.putDouble( "windowY", bounds.getY() );
70
      state.putDouble( "windowWidth", bounds.getWidth() );
71
      state.putDouble( "windowHeight", bounds.getHeight() );
72
    }
73
    state.putBoolean( "windowMaximized", stage.isMaximized() );
74
    state.putBoolean( "windowFullScreen", stage.isFullScreen() );
75
  }
76
77
  private void restore() {
78
    double x = state.getDouble( "windowX", Double.NaN );
79
    double y = state.getDouble( "windowY", Double.NaN );
80
    double w = state.getDouble( "windowWidth", Double.NaN );
81
    double h = state.getDouble( "windowHeight", Double.NaN );
82
    boolean maximized = state.getBoolean( "windowMaximized", false );
83
    boolean fullScreen = state.getBoolean( "windowFullScreen", false );
84
85
    if( !Double.isNaN( x ) && !Double.isNaN( y ) ) {
86
      stage.setX( x );
87
      stage.setY( y );
88
    } // else: default behavior is center on screen
89
90
    if( !Double.isNaN( w ) && !Double.isNaN( h ) ) {
91
      stage.setWidth( w );
92
      stage.setHeight( h );
93
    } // else: default behavior is use scene size
94
95
    if( fullScreen != stage.isFullScreen() ) {
96
      stage.setFullScreen( fullScreen );
97
    }
98
    if( maximized != stage.isMaximized() ) {
99
      stage.setMaximized( maximized );
100
    }
101
  }
102
103
  /**
104
   * Remembers the window bounds when the window is not iconified, maximized or
105
   * in fullScreen.
106
   */
107
  private void boundsChanged() {
108
    // avoid too many (and useless) runLater() invocations
109
    if( runLaterPending ) {
110
      return;
111
    }
112
    runLaterPending = true;
113
114
    // must use runLater() to ensure that change of all properties
115
    // (x, y, width, height, iconified, maximized and fullScreen)
116
    // has finished
117
    Platform.runLater( () -> {
118
      runLaterPending = false;
119
120
      if( isNormalState() ) {
121
        normalBounds = getStageBounds();
122
      }
123
    } );
124
  }
125
126
  private boolean isNormalState() {
127
    return !stage.isIconified() && !stage.isMaximized() && !stage.isFullScreen();
128
  }
129
130
  private Rectangle getStageBounds() {
131
    return new Rectangle( stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight() );
132
  }
133
}
1134
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.Set;
31
import java.util.prefs.Preferences;
32
import javafx.geometry.Orientation;
33
import javafx.scene.Node;
34
import javafx.scene.control.ScrollBar;
35
36
/**
37
 * @author Karl Tauber
38
 */
39
public class Utils {
40
41
  public static boolean safeEquals( Object o1, Object o2 ) {
42
    if( o1 == o2 ) {
43
      return true;
44
    }
45
    if( o1 == null || o2 == null ) {
46
      return false;
47
    }
48
    return o1.equals( o2 );
49
  }
50
51
  public static boolean isNullOrEmpty( String s ) {
52
    return s == null || s.isEmpty();
53
  }
54
55
  public static String ltrim( final String s ) {
56
    int i = 0;
57
58
    while( i < s.length() && Character.isWhitespace( s.charAt( i ) ) ) {
59
      i++;
60
    }
61
62
    return s.substring( i );
63
  }
64
65
  public static String rtrim( final String s ) {
66
    int i = s.length() - 1;
67
68
    while( i >= 0 && Character.isWhitespace( s.charAt( i ) ) ) {
69
      i--;
70
    }
71
72
    return s.substring( 0, i + 1 );
73
  }
74
75
  public static void putPrefs( Preferences prefs, String key, String value, String def ) {
76
    if( value != def && !value.equals( def ) ) {
77
      prefs.put( key, value );
78
    } else {
79
      prefs.remove( key );
80
    }
81
  }
82
83
  public static void putPrefsInt( Preferences prefs, String key, int value, int def ) {
84
    if( value != def ) {
85
      prefs.putInt( key, value );
86
    } else {
87
      prefs.remove( key );
88
    }
89
  }
90
91
  public static void putPrefsBoolean( Preferences prefs, String key, boolean value, boolean def ) {
92
    if( value != def ) {
93
      prefs.putBoolean( key, value );
94
    } else {
95
      prefs.remove( key );
96
    }
97
  }
98
99
  public static String[] getPrefsStrings( final Preferences prefs, String key ) {
100
    final ArrayList<String> arr = new ArrayList<>();
101
102
    for( int i = 0; i < 10000; i++ ) {
103
      final String s = prefs.get( key + (i + 1), null );
104
105
      if( s == null ) {
106
        break;
107
      }
108
      arr.add( s );
109
    }
110
111
    return arr.toArray( new String[ arr.size() ] );
112
  }
113
114
  public static void putPrefsStrings( Preferences prefs, String key, String[] strings ) {
115
    for( int i = 0; i < strings.length; i++ ) {
116
      prefs.put( key + (i + 1), strings[ i ] );
117
    }
118
119
    for( int i = strings.length; prefs.get( key + (i + 1), null ) != null; i++ ) {
120
      prefs.remove( key + (i + 1) );
121
    }
122
  }
123
124
  public static ScrollBar findVScrollBar( Node node ) {
125
    final Set<Node> scrollBars = node.lookupAll( ".scroll-bar" );
126
127
    for( final Node scrollBar : scrollBars ) {
128
      if( scrollBar instanceof ScrollBar
129
        && ((ScrollBar)scrollBar).getOrientation() == Orientation.VERTICAL ) {
130
        return (ScrollBar)scrollBar;
131
      }
132
    }
133
134
    return null;
135
  }
136
}
1137
A src/main/java/com/scrivenvar/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.yaml;
29
30
import com.fasterxml.jackson.core.JsonGenerationException;
31
import com.fasterxml.jackson.core.ObjectCodec;
32
import com.fasterxml.jackson.core.io.IOContext;
33
import com.fasterxml.jackson.databind.JsonNode;
34
import com.fasterxml.jackson.databind.ObjectMapper;
35
import com.fasterxml.jackson.databind.node.ObjectNode;
36
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
37
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
38
import static com.scrivenvar.Constants.SEPARATOR;
39
import com.scrivenvar.decorators.VariableDecorator;
40
import com.scrivenvar.decorators.YamlVariableDecorator;
41
import java.io.IOException;
42
import java.io.InputStream;
43
import java.io.Writer;
44
import java.security.InvalidParameterException;
45
import java.text.MessageFormat;
46
import java.util.HashMap;
47
import java.util.Map;
48
import java.util.Map.Entry;
49
import java.util.regex.Matcher;
50
import java.util.regex.Pattern;
51
import org.yaml.snakeyaml.DumperOptions;
52
53
/**
54
 * <p>
55
 * This program loads a YAML document into memory, scans for variable
56
 * declarations, then substitutes any self-referential values back into the
57
 * document. Its output is the given YAML document without any variables.
58
 * Variables in the YAML document are denoted using a bracketed dollar symbol
59
 * syntax. For example: $field.name$. Some nomenclature to keep from going
60
 * squirrely, consider:
61
 * </p>
62
 *
63
 * <pre>
64
 *   root:
65
 *     node:
66
 *       name: $field.name$
67
 *   field:
68
 *     name: Alan Turing
69
 * </pre>
70
 *
71
 * The various components of the given YAML are called:
72
 *
73
 * <ul>
74
 * <li><code>$field.name$</code> - delimited reference</li>
75
 * <li><code>field.name</code> - reference</li>
76
 * <li><code>name</code> - YAML field</li>
77
 * <li><code>Alan Turing</code> - (dereferenced) field value</li>
78
 * </ul>
79
 *
80
 * @author White Magic Software, Ltd.
81
 */
82
public class YamlParser {
83
84
  private final static int GROUP_DELIMITED = 1;
85
  private final static int GROUP_REFERENCE = 2;
86
87
  private final static VariableDecorator VARIABLE_DECORATOR
88
    = new YamlVariableDecorator();
89
90
  /**
91
   * Compiled version of DEFAULT_REGEX.
92
   */
93
  private final static Pattern REGEX_PATTERN
94
    = Pattern.compile( YamlVariableDecorator.REGEX );
95
96
  /**
97
   * Should be JsonPointer.SEPARATOR, but Jackson YAML uses magic values.
98
   */
99
  private final static char SEPARATOR_YAML = '/';
100
101
  /**
102
   * Start of the Universe (the YAML document node that contains all others).
103
   */
104
  private ObjectNode documentRoot;
105
106
  /**
107
   * Map of references to dereferenced field values.
108
   */
109
  private Map<String, String> references;
110
111
  public YamlParser() {
112
  }
113
114
  /**
115
   * Returns the given string with all the delimited references swapped with
116
   * their recursively resolved values.
117
   *
118
   * @param text The text to parse with zero or more delimited references to
119
   * replace.
120
   *
121
   * @return The substituted value.
122
   *
123
   * @throws InvalidParameterException The text has no associated value.
124
   */
125
  public String substitute( String text ) {
126
    final Matcher matcher = patternMatch( text );
127
    final Map<String, String> map = getReferences();
128
129
    while( matcher.find() ) {
130
      final String key = matcher.group( GROUP_DELIMITED );
131
      final String value = map.get( key );
132
133
      if( value == null ) {
134
        missing( text );
135
      } else {
136
        text = text.replace( key, value );
137
      }
138
    }
139
140
    return text;
141
  }
142
143
  /**
144
   * Returns all the strings with their values resolved in a flat hierarchy.
145
   * This copies all the keys and resolved values into a new map.
146
   *
147
   * @return The new map created with all values having been resolved,
148
   * recursively.
149
   *
150
   * @throws InvalidParameterException A key in the map has no associated value.
151
   */
152
  public Map<String, String> createResolvedMap() {
153
    final Map<String, String> map = new HashMap<>( 1024 );
154
155
    resolve( getDocumentRoot(), "", map );
156
157
    return map;
158
  }
159
160
  /**
161
   * Iterate over a given root node (at any level of the tree) and adapt each
162
   * leaf node.
163
   *
164
   * @param rootNode A JSON node (YAML node) to adapt.
165
   */
166
  private void resolve(
167
    final JsonNode rootNode, final String path, final Map<String, String> map ) {
168
169
    rootNode.fields().forEachRemaining(
170
      (Entry<String, JsonNode> leaf) -> resolve( leaf, path, map )
171
    );
172
  }
173
174
  /**
175
   * Recursively adapt each rootNode to a corresponding rootItem.
176
   *
177
   * @param rootNode The node to adapt.
178
   */
179
  private void resolve(
180
    final Entry<String, JsonNode> rootNode, final String path, final Map<String, String> map ) {
181
    final JsonNode leafNode = rootNode.getValue();
182
    final String key = rootNode.getKey();
183
184
    if( leafNode.isValueNode() ) {
185
      final String value = rootNode.getValue().asText();
186
187
      map.put( VARIABLE_DECORATOR.decorate( path + key ), substitute( value ) );
188
    }
189
190
    if( leafNode.isObject() ) {
191
      resolve( leafNode, path + key + SEPARATOR, map );
192
    }
193
  }
194
195
  /**
196
   * Reads the first document from the given stream of YAML data and returns a
197
   * corresponding object that represents the YAML hierarchy. The calling class
198
   * is responsible for closing the stream. Calling classes should use
199
   * <code>JsonNode.fields()</code> to walk through the YAML tree of fields.
200
   *
201
   * @param in The input stream containing YAML content.
202
   *
203
   * @return An object hierarchy to represent the content.
204
   *
205
   * @throws IOException Could not read the stream.
206
   */
207
  public JsonNode process( final InputStream in ) throws IOException {
208
209
    final ObjectNode root = (ObjectNode)getObjectMapper().readTree( in );
210
    setDocumentRoot( root );
211
    process( root );
212
    return getDocumentRoot();
213
  }
214
215
  /**
216
   * Iterate over a given root node (at any level of the tree) and process each
217
   * leaf node.
218
   *
219
   * @param root A node to process.
220
   */
221
  private void process( final JsonNode root ) {
222
    root.fields().forEachRemaining( this::process );
223
  }
224
225
  /**
226
   * Process the given field, which is a named node. This is where the
227
   * application does the up-front work of mapping references to their fully
228
   * recursively dereferenced values.
229
   *
230
   * @param field The named node.
231
   */
232
  private void process( final Entry<String, JsonNode> field ) {
233
    final JsonNode node = field.getValue();
234
235
    if( node.isObject() ) {
236
      process( node );
237
    } else {
238
      final JsonNode fieldValue = field.getValue();
239
240
      // Only basic data types can be parsed into variable values. For
241
      // node structures, YAML has a built-in mechanism.
242
      if( fieldValue.isValueNode() ) {
243
        try {
244
          resolve( fieldValue.asText() );
245
        } catch( StackOverflowError e ) {
246
          throw new IllegalArgumentException(
247
            "Unresolvable: " + node.textValue() + " = " + fieldValue );
248
        }
249
      }
250
    }
251
  }
252
253
  /**
254
   * Inserts the delimited references and field values into the cache. This will
255
   * overwrite existing references.
256
   *
257
   * @param fieldValue YAML field containing zero or more delimited references.
258
   * If it contains a delimited reference, the parameter is modified with the
259
   * dereferenced value before it is returned.
260
   *
261
   * @return fieldValue without delimited references.
262
   */
263
  private String resolve( String fieldValue ) {
264
    final Matcher matcher = patternMatch( fieldValue );
265
266
    while( matcher.find() ) {
267
      final String delimited = matcher.group( GROUP_DELIMITED );
268
      final String reference = matcher.group( GROUP_REFERENCE );
269
      final String dereference = resolve( lookup( reference ) );
270
271
      fieldValue = fieldValue.replace( delimited, dereference );
272
273
      // This will perform some superfluous calls by overwriting existing
274
      // items in the delimited reference map.
275
      put( delimited, dereference );
276
    }
277
278
    return fieldValue;
279
  }
280
281
  /**
282
   * Inserts a key/value pair into the references map. The map retains
283
   * references and dereferenced values found in the YAML. If the reference
284
   * already exists, this will overwrite with a new value.
285
   *
286
   * @param delimited The variable name.
287
   * @param dereferenced The resolved value.
288
   */
289
  private void put( String delimited, String dereferenced ) {
290
    if( dereferenced.isEmpty() ) {
291
      missing( delimited );
292
    } else {
293
      getReferences().put( delimited, dereferenced );
294
    }
295
  }
296
297
  /**
298
   * Writes the modified YAML document to standard output.
299
   */
300
  private void writeDocument() throws IOException {
301
    getObjectMapper().writeValue( System.out, getDocumentRoot() );
302
  }
303
304
  /**
305
   * Called when a delimited reference is dereferenced to an empty string. This
306
   * should produce a warning for the user.
307
   *
308
   * @param delimited Delimited reference with no derived value.
309
   */
310
  private void missing( final String delimited ) {
311
    throw new InvalidParameterException(
312
      MessageFormat.format( "Missing value for '{0}'.", delimited ) );
313
  }
314
315
  /**
316
   * Returns a REGEX_PATTERN matcher for the given text.
317
   *
318
   * @param text The text that contains zero or more instances of a
319
   * REGEX_PATTERN that can be found using the regular expression.
320
   */
321
  private Matcher patternMatch( String text ) {
322
    return getPattern().matcher( text );
323
  }
324
325
  /**
326
   * Finds the YAML value for a reference.
327
   *
328
   * @param reference References a value in the YAML document.
329
   *
330
   * @return The dereferenced value.
331
   */
332
  private String lookup( final String reference ) {
333
    return getDocumentRoot().at( asPath( reference ) ).asText();
334
  }
335
336
  /**
337
   * Converts a reference (not delimited) to a path that can be used to find a
338
   * value that should exist inside the YAML document.
339
   *
340
   * @param reference The reference to convert to a YAML document path.
341
   *
342
   * @return The reference with a leading slash and its separator characters
343
   * converted to slashes.
344
   */
345
  private String asPath( final String reference ) {
346
    return SEPARATOR_YAML + reference.replace( getDelimitedSeparator(), SEPARATOR_YAML );
347
  }
348
349
  /**
350
   * Sets the parent node for the entire YAML document tree.
351
   *
352
   * @param documentRoot The parent node.
353
   */
354
  private void setDocumentRoot( ObjectNode documentRoot ) {
355
    this.documentRoot = documentRoot;
356
  }
357
358
  /**
359
   * Returns the parent node for the entire YAML document tree.
360
   *
361
   * @return The parent node.
362
   */
363
  private ObjectNode getDocumentRoot() {
364
    return this.documentRoot;
365
  }
366
367
  /**
368
   * Returns the compiled regular expression REGEX_PATTERN used to match
369
   * delimited references.
370
   *
371
   * @return A compiled regex for use with the Matcher.
372
   */
373
  private Pattern getPattern() {
374
    return REGEX_PATTERN;
375
  }
376
377
  /**
378
   * Returns the list of references mapped to dereferenced values.
379
   *
380
   * @return
381
   */
382
  private Map<String, String> getReferences() {
383
    if( this.references == null ) {
384
      this.references = createReferences();
385
    }
386
387
    return this.references;
388
  }
389
390
  /**
391
   * Subclasses can override this method to insert their own map.
392
   *
393
   * @return An empty HashMap, never null.
394
   */
395
  protected Map<String, String> createReferences() {
396
    return new HashMap<>();
397
  }
398
399
  private class ResolverYAMLFactory extends YAMLFactory {
400
401
    @Override
402
    protected YAMLGenerator _createGenerator(
403
      final Writer out, final IOContext ctxt ) throws IOException {
404
405
      return new ResolverYAMLGenerator(
406
        ctxt, _generatorFeatures, _yamlGeneratorFeatures, _objectCodec,
407
        out, _version );
408
    }
409
  }
410
411
  private class ResolverYAMLGenerator extends YAMLGenerator {
412
413
    public ResolverYAMLGenerator(
414
      final IOContext ctxt,
415
      final int jsonFeatures,
416
      final int yamlFeatures,
417
      final ObjectCodec codec,
418
      final Writer out,
419
      final DumperOptions.Version version ) throws IOException {
420
421
      super( ctxt, jsonFeatures, yamlFeatures, codec, out, version );
422
    }
423
424
    @Override
425
    public void writeString( final String text )
426
      throws IOException, JsonGenerationException {
427
      super.writeString( substitute( text ) );
428
    }
429
  }
430
431
  private YAMLFactory getYAMLFactory() {
432
    return new ResolverYAMLFactory();
433
  }
434
435
  private ObjectMapper getObjectMapper() {
436
    return new ObjectMapper( getYAMLFactory() );
437
  }
438
439
  /**
440
   * Returns the character used to separate YAML paths within delimited
441
   * references. This will return only the first character of the command line
442
   * parameter, if the default is overridden.
443
   *
444
   * @return A period by default.
445
   */
446
  private char getDelimitedSeparator() {
447
    return SEPARATOR.charAt( 0 );
448
  }
449
}
1450
A src/main/java/com/scrivenvar/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.yaml;
29
30
import com.fasterxml.jackson.databind.JsonNode;
31
import com.scrivenvar.ui.VariableTreeItem;
32
import java.io.IOException;
33
import java.io.InputStream;
34
import java.util.Map.Entry;
35
import javafx.scene.control.TreeItem;
36
import javafx.scene.control.TreeView;
37
38
/**
39
 * Transforms a JsonNode hierarchy into a tree that can be displayed in a user
40
 * interface.
41
 *
42
 * @author White Magic Software, Ltd.
43
 */
44
public class YamlTreeAdapter {
45
46
  private YamlParser yamlParser;
47
48
  public YamlTreeAdapter( final YamlParser parser ) {
49
    setYamlParser( parser );
50
  }
51
52
  /**
53
   * Converts a YAML document to a TreeView based on the document keys. Only the
54
   * first document in the stream is adapted. This does not close the stream.
55
   *
56
   * @param in Contains a YAML document.
57
   * @param name Name of the root TreeItem.
58
   *
59
   * @return A TreeView populated with all the keys in the YAML document.
60
   *
61
   * @throws IOException Could not read from the stream.
62
   */
63
  public TreeView<String> adapt( final InputStream in, final String name )
64
    throws IOException {
65
66
    final JsonNode rootNode = getYamlParser().process( in );
67
    final TreeItem<String> rootItem = createTreeItem( name );
68
69
    rootItem.setExpanded( true );
70
    adapt( rootNode, rootItem );
71
    return new TreeView<>( rootItem );
72
  }
73
74
  /**
75
   * Iterate over a given root node (at any level of the tree) and adapt each
76
   * leaf node.
77
   *
78
   * @param rootNode A JSON node (YAML node) to adapt.
79
   * @param rootItem The tree item to use as the root when processing the node.
80
   */
81
  private void adapt(
82
    final JsonNode rootNode, final TreeItem<String> rootItem ) {
83
84
    rootNode.fields().forEachRemaining(
85
      (Entry<String, JsonNode> leaf) -> adapt( leaf, rootItem )
86
    );
87
  }
88
89
  /**
90
   * Recursively adapt each rootNode to a corresponding rootItem.
91
   *
92
   * @param rootNode The node to adapt.
93
   * @param rootItem The item to adapt using the node's key.
94
   */
95
  private void adapt(
96
    final Entry<String, JsonNode> rootNode, final TreeItem<String> rootItem ) {
97
98
    final JsonNode leafNode = rootNode.getValue();
99
    final String key = rootNode.getKey();
100
    final TreeItem<String> leaf = createTreeItem( key );
101
102
    if( leafNode.isValueNode() ) {
103
      leaf.getChildren().add( createTreeItem( rootNode.getValue().asText() ) );
104
    }
105
106
    rootItem.getChildren().add( leaf );
107
108
    if( leafNode.isObject() ) {
109
      adapt( leafNode, leaf );
110
    }
111
  }
112
113
  /**
114
   * Creates a new tree item that can be added to the tree view.
115
   *
116
   * @param value The node's value.
117
   *
118
   * @return A new tree item node, never null.
119
   */
120
  private TreeItem<String> createTreeItem( final String value ) {
121
    return new VariableTreeItem<>( value );
122
  }
123
124
  private YamlParser getYamlParser() {
125
    return this.yamlParser;
126
  }
127
128
  private void setYamlParser( final YamlParser yamlParser ) {
129
    this.yamlParser = yamlParser;
130
  }
131
132
}
1133
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/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
}
1305
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
# Filename Extensions
4
#
5
# ########################################################################
6
7
# Comma-separated list of filename extensions.
8
Dialog.file.choose.filter.ext.markdown=*.Rmd,*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt
9
Dialog.file.choose.filter.ext.definition=*.yml,*.yaml,*.properties,*.props
10
Dialog.file.choose.filter.ext.xml=*.xml,*.Rxml
11
Dialog.file.choose.filter.ext.all=*.*
12
13
# ########################################################################
14
#
15
# Variable Name Editor
16
#
17
# ########################################################################
18
19
# Maximum number of characters for a variable name. A variable is defined
20
# as one or more non-whitespace characters up to this maximum length.
21
editor.variable.maxLength=256
22
23
# ########################################################################
24
#
25
# Dialog Preferences
26
#
27
# ########################################################################
28
29
# docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ButtonBar.html
30
dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R
31
dialog.alert.button.order.linux=L_HE+UNYACBXIO_R
32
dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R
33
34
# Ensures a consistent button order for alert dialogs across platforms (because
35
# the default button order on Linux defies all logic). Power to the people.
36
dialog.alert.button.order=${dialog.alert.button.order.windows}
137
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