Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
A R/conversion.R
1
# -----------------------------------------------------------------------------
2
# Copyright 2020, White Magic Software, Ltd.
3
# 
4
# Permission is hereby granted, free of charge, to any person obtaining
5
# a copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
# 
12
# The above copyright notice and this permission notice shall be
13
# included in all copies or substantial portions of the Software.
14
# 
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# Substitute R expressions in a document with their evaluated value. The
26
# anchor variable must be set for functions that use relative dates.
27
# -----------------------------------------------------------------------------
28
29
# -----------------------------------------------------------------------------
30
# Evaluates an expression; writes s if there is no expression.
31
# -----------------------------------------------------------------------------
32
x <- function( s ) {
33
  tryCatch( {
34
    r = eval( parse( text = s ) )
35
36
    # If the result isn't primitive, then it was probably parsed into
37
    # an unprintable object (e.g., "gray" becomes a colour). In those
38
    # cases, return the original text string. Otherwise, an atomic
39
    # value means a primitive type (string, integer, etc.) that can be
40
    # written directly into the document.
41
    ifelse( is.atomic( r ), r, s );
42
  },
43
  warning = function( w ) { s },
44
  error = function( e ) { s } )
45
}
46
47
# -----------------------------------------------------------------------------
48
# Returns a date offset by a given number of days, relative to the given
49
# date (d). This does not use the anchor, but is used to get the anchor's
50
# value as a date.
51
# -----------------------------------------------------------------------------
52
when <- function( d, n = 0, format = "%Y-%m-%d" ) {
53
  as.Date( d, format = format ) + x( n )
54
}
55
56
# -----------------------------------------------------------------------------
57
# Full date (s) offset by an optional number of days before or after.
58
# This will remove leading zeros (applying leading spaces instead, which
59
# are ignored by any worthwhile typesetting engine).
60
# -----------------------------------------------------------------------------
61
annal <- function( days = 0, format = "%Y-%m-%d", oformat = "%B %d, %Y" ) {
62
  format( when( anchor, days ), format = oformat )
63
}
64
65
# -----------------------------------------------------------------------------
66
# Extracts the year from a date string.
67
# -----------------------------------------------------------------------------
68
year <- function( days = 0, format = "%Y-%m-%d" ) {
69
  annal( days, format, "%Y" )
70
}
71
72
# -----------------------------------------------------------------------------
73
# Day of the week (in days since the anchor date).
74
# -----------------------------------------------------------------------------
75
weekday <- function( n ) {
76
  weekdays( when( anchor, n ) )
77
}
78
79
# -----------------------------------------------------------------------------
80
# String concatenate function alias because paste0 is a terrible name.
81
# -----------------------------------------------------------------------------
82
concat <- paste0
83
84
# -----------------------------------------------------------------------------
85
# Translates a number from digits to words using Chicago Manual of Style.
86
# This does not translate numbers greater than one hundred. If ordinal
87
# is TRUE, this will return the ordinal name. This will not produce ordinals
88
# for numbers greater than 100.
89
# -----------------------------------------------------------------------------
90
cms <- function( n, ordinal = FALSE ) {
91
  n <- x( n )
92
93
  if( n == 0 ) {
94
    if( ordinal ) {
95
      return( "zeroth" )
96
    }
97
98
    return( "zero" )
99
  }
100
101
  # Concatenate this a little later.
102
  if( n < 0 ) {
103
    result = "negative "
104
    n = abs( n )
105
  }
106
107
  # Do not spell out numbers greater than one hundred.
108
  if( n > 100 ) {
109
    # Comma-separated numbers.
110
    return( commas( n ) )
111
  }
112
113
  # Don't go beyond 100.
114
  if( n == 100 ) {
115
    if( ordinal ) {
116
      return( "one hundredth" )
117
    }
118
119
    return( "one hundred" )
120
  }
121
122
  # Samuel Langhorne Clemens noted English has too many exceptions.
123
  small = c(
124
    "one", "two", "three", "four", "five",
125
    "six", "seven", "eight", "nine", "ten",
126
    "eleven", "twelve", "thirteen", "fourteen", "fifteen",
127
    "sixteen", "seventeen", "eighteen", "nineteen"
128
  )
129
130
  ord_small = c(
131
    "first", "second", "third", "fourth", "fifth",
132
    "sixth", "seventh", "eighth", "ninth", "tenth",
133
    "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth",
134
    "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"
135
  )
136
137
  # After this, the number (n) is between 20 and 99.
138
  if( n < 20 ) {
139
    if( ordinal ) {
140
      return( .subset( ord_small, n %% 100 ) )
141
    }
142
143
    return( .subset( small, n %% 100 ) )
144
  }
145
146
  tens = c( "",
147
    "twenty", "thirty", "forty", "fifty",
148
    "sixty", "seventy", "eighty", "ninety"
149
  )
150
151
  ord_tens = c( "",
152
    "twentieth", "thirtieth", "fortieth", "fiftieth",
153
    "sixtieth", "seventieth", "eightieth", "ninetieth"
154
  )
155
156
  ones_index = n %% 10
157
  n = n %/% 10
158
159
  # No number in the ones column, so the number must be a multiple of ten.
160
  if( ones_index == 0 ) {
161
    if( ordinal ) {
162
      return( .subset( ord_tens, n ) )
163
    }
164
165
    return( .subset( tens, n ) )
166
  }
167
168
  # Find the value from the ones column.
169
  if( ordinal ) {
170
    unit_1 = .subset( ord_small, ones_index )
171
  }
172
  else {
173
    unit_1 = .subset( small, ones_index )
174
  }
175
176
  # Find the tens column.
177
  unit_10 = .subset( tens, n )
178
179
  # Hyphenate the tens and the ones together.
180
  concat( unit_10, concat( "-", unit_1 ) )
181
}
182
183
# -----------------------------------------------------------------------------
184
# Returns a number as a comma-delimited string. This is a work-around
185
# until Renjin fixes https://github.com/bedatadriven/renjin/issues/338
186
# -----------------------------------------------------------------------------
187
commas <- function( n ) {
188
  n <- x( n )
189
190
  s <- sprintf( "%03.0f", n %% 1000 )
191
  n <- n %/% 1000
192
193
  while( n > 0 ) {
194
    s <- concat( sprintf( "%03.0f", n %% 1000 ), ',', s )
195
    n <- n %/% 1000
196
  }
197
198
  gsub( '^0*', '', s )
199
}
200
201
# -----------------------------------------------------------------------------
202
# Returns a human-readable string that provides the elapsed time between
203
# two numbers in terms of years, months, and days. If any unit value is zero,
204
# the unit is not included. The words (year, month, day) are pluralised
205
# according to English grammar. The numbers are written out according to
206
# Chicago Manual of Style. This applies the serial comma.
207
#
208
# Both numbers are offsets relative to the anchor date.
209
#
210
# If all unit values are zero, this returns s ("same day" by default).
211
#
212
# If the start date (began) is greater than end date (ended), the dates are
213
# swapped before calculations are performed. This allows any two dates
214
# to be compared and positive unit values are always returned.
215
# -----------------------------------------------------------------------------
216
elapsed <- function( began, ended, s = "same day" ) {
217
  began = when( anchor, began )
218
  ended = when( anchor, ended )
219
220
  # Swap the dates if the end date comes before the start date.
221
  if( as.integer( ended - began ) < 0 ) {
222
    tempd = began
223
    began = ended
224
    ended = tempd
225
  }
226
227
  # Calculate number of elapsed years.
228
  years = length( seq( from = began, to = ended, by = 'year' ) ) - 1
229
230
  # Move the start date up by the number of elapsed years.
231
  if( years > 0 ) {
232
    began = seq( began, length = 2, by = concat( years, " years" ) )[2]
233
    years = pl.numeric( "year", years )
234
  }
235
  else {
236
    # Zero years.
237
    years = ""
238
  }
239
240
  # Calculate number of elapsed months, excluding years.
241
  months = length( seq( from = began, to = ended, by = 'month' ) ) - 1
242
243
  # Move the start date up by the number of elapsed months
244
  if( months > 0 ) {
245
    began = seq( began, length = 2, by = concat( months, " months" ) )[2]
246
    months = pl.numeric( "month", months )
247
  }
248
  else {
249
    # Zero months
250
    months = ""
251
  }
252
253
  # Calculate number of elapsed days, excluding months and years.
254
  days = length( seq( from = began, to = ended, by = 'day' ) ) - 1
255
256
  if( days > 0 ) {
257
    days = pl.numeric( "day", days )
258
  }
259
  else {
260
    # Zero days
261
    days = ""
262
  }
263
264
  if( years <= 0 && months <= 0 && days <= 0 ) {
265
    return( s )
266
  }
267
268
  # Put them all in a vector, then remove the empty values.
269
  s <- c( years, months, days )
270
  s <- s[ s != "" ]
271
272
  r <- paste( s, collapse = ", " )
273
274
  # If all three items are present, replace the last comma with ", and".
275
  if( length( s ) > 2 ) {
276
    return( gsub( "(.*),", "\\1, and", r ) )
277
  }
278
279
  # Does nothing if no commas are present.
280
  gsub( "(.*),", "\\1 and", r )
281
}
282
283
# -----------------------------------------------------------------------------
284
# Returns the number (n) in English followed by the plural or singular
285
# form of the given string (s; resumably a noun), if applicable, according
286
# to English grammar. That is, pl.numeric( "wolf", 5 ) will return
287
# "five wolves".
288
# -----------------------------------------------------------------------------
289
pl.numeric <- function( s, n ) {
290
  concat( cms( n ), concat( " ", pluralise( s, n ) ) )
291
}
292
293
# -----------------------------------------------------------------------------
294
# Pluralise s if n is not equal to 1.
295
# -----------------------------------------------------------------------------
296
pl <- function( s, n=2 ) {
297
  pluralize( s, x( n ) )
298
}
299
300
# -----------------------------------------------------------------------------
301
# Name of the season, starting with an capital letter.
302
# -----------------------------------------------------------------------------
303
season <- function( n, format = "%Y-%m-%d" ) {
304
  WS <- as.Date("2016-12-15", "%Y-%m-%d") # Winter Solstice
305
  SE <- as.Date("2016-03-15", "%Y-%m-%d") # Spring Equinox
306
  SS <- as.Date("2016-06-15", "%Y-%m-%d") # Summer Solstice
307
  AE <- as.Date("2016-09-15", "%Y-%m-%d") # Autumn Equinox
308
309
  d <- when( anchor, n )
310
  d <- as.Date( strftime( d, format="2016-%m-%d" ) )
311
312
  ifelse( d >= WS | d < SE, "Winter",
313
    ifelse( d >= SE & d < SS, "Spring",
314
      ifelse( d >= SS & d < AE, "Summer", "Autumn" )
315
    )
316
  )
317
}
318
319
# -----------------------------------------------------------------------------
320
# Converts the first letter in a string to lowercase
321
# -----------------------------------------------------------------------------
322
lc <- function( s ) {
323
  concat( tolower( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) )
324
}
325
326
# -----------------------------------------------------------------------------
327
# Converts the entire string to lowercase
328
# -----------------------------------------------------------------------------
329
lower <- tolower
330
331
# -----------------------------------------------------------------------------
332
# Converts the first letter in a string to uppercase
333
# -----------------------------------------------------------------------------
334
uc <- function( s ) {
335
  concat( toupper( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) )
336
}
337
338
# -----------------------------------------------------------------------------
339
# Returns the number of days between the given dates.
340
# -----------------------------------------------------------------------------
341
days <- function( d1, d2, format = "%Y-%m-%d" ) {
342
  dates = c( d1, d2 )
343
  dt = strptime( dates, format = format )
344
  as.integer( difftime( dates[2], dates[1], units = "days" ) )
345
}
346
347
# -----------------------------------------------------------------------------
348
# Returns the number of years elapsed.
349
# -----------------------------------------------------------------------------
350
years <- function( began, ended ) {
351
  began = when( anchor, began )
352
  ended = when( anchor, ended )
353
354
  # Swap the dates if the end date comes before the start date.
355
  if( as.integer( ended - began ) < 0 ) {
356
    tempd = began
357
    began = ended
358
    ended = tempd
359
  }
360
361
  # Calculate number of elapsed years.
362
  length( seq( from = began, to = ended, by = 'year' ) ) - 1
363
}
364
365
# -----------------------------------------------------------------------------
366
# Full name of the month, starting with a capital letter.
367
# -----------------------------------------------------------------------------
368
month <- function( n ) {
369
  # Faster than month.name[ x( n ) ]
370
  .subset( month.name, x( n ) )
371
}
372
373
# -----------------------------------------------------------------------------
374
# -----------------------------------------------------------------------------
375
money <- function( n ) {
376
  commas( x( n ) )
377
}
378
379
# -----------------------------------------------------------------------------
380
# -----------------------------------------------------------------------------
381
timeline <- function( n ) {
382
  concat( weekday( n ), ", ", annal( n ), " (", season( n ), ")" )
383
}
384
385
# -----------------------------------------------------------------------------
386
# Rounds to the nearest base value (e.g., round to nearest 10).
387
#
388
# @param base The nearest value to round to.
389
# -----------------------------------------------------------------------------
390
round.up <- function( n, base = 5 ) {
391
  base * round( x( n ) / base )
392
}
393
394
# -----------------------------------------------------------------------------
395
# Computes linear distance between two points using Haversine formula.
396
# Although Earth is an oblate spheroid, this will produce results close
397
# enough for most purposes.
398
#
399
# @param lat1/lon1 The source latitude and longitude.
400
# @param lat2/lon2 The destination latitude and longitude.
401
# @param radius The radius of the sphere.
402
#
403
# @return The distance between the two coordinates in meters.
404
# -----------------------------------------------------------------------------
405
haversine <- function( lat1, lon1, lat2, lon2, radius = 6371 ) {
406
  # Convert decimal degrees to radians
407
  lon1 = lon1 * pi / 180
408
  lon2 = lon2 * pi / 180
409
  lat1 = lat1 * pi / 180
410
  lat2 = lat2 * pi / 180
411
412
  # Haversine formula
413
  dlon = lon2 - lon1
414
  dlat = lat2 - lat1
415
  a = sin( dlat / 2 ) ** 2 + cos( lat1 ) * cos( lat2 ) * sin( dlon / 2 ) ** 2
416
  c = 2 * atan2( sqrt( a ), sqrt( 1-a ) )
417
418
  return( radius * c * 1000 )
419
}
420
1421
A R/csv.R
1
# -----------------------------------------------------------------------------
2
# Copyright 2020, White Magic Software, Ltd.
3
# 
4
# Permission is hereby granted, free of charge, to any person obtaining
5
# a copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
# 
12
# The above copyright notice and this permission notice shall be
13
# included in all copies or substantial portions of the Software.
14
# 
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# Converts CSV to Markdown.
26
#
27
# Reads a CSV file and converts the contents to a Markdown table. The
28
# file must be in the working directory as specified by setwd.
29
#
30
# @param f The filename to convert.
31
# @param decimals Rounded decimal places (default 1).
32
# @param totals Include total sums (default TRUE).
33
# @param align Right-align numbers (default TRUE).
34
# -----------------------------------------------------------------------------
35
csv2md <- function( f, decimals = 2, totals = T, align = T ) {
36
  # Read the CVS data from the file; ensure strings become characters.
37
  df <- read.table( f, sep=',', header=T, stringsAsFactors=F )
38
39
  if( totals ) {
40
    # Determine what columns can be summed.
41
    number <- which( unlist( lapply( df, is.numeric ) ) )
42
43
    # Use colSums when more than one summable column exists.
44
    if( length( number ) > 1 ) {
45
      f.sum <- colSums
46
    }
47
    else {
48
      f.sum <- sum
49
    }
50
51
    # Calculate the sum of all the summable columns and insert the
52
    # results back into the data frame.
53
    df[ (nrow( df ) + 1), number ] <- f.sum( df[, number], na.rm=TRUE )
54
55
    # pluralise would be heavyweight here.
56
    if( length( number ) > 1 ) {
57
      t <- "**Totals**"
58
    }
59
    else {
60
      t <- "**Total**"
61
    }
62
63
    # Change the first column of the last line to "Total(s)".
64
    df[ nrow( df ), 1 ] <- t
65
66
    # Don't clutter the output with "NA" text.
67
    df[ is.na( df ) ] <- ""
68
  }
69
70
  if( align ) {
71
    is.char <- vapply( df, is.character, logical( 1 ) )
72
    dashes <- paste( ifelse( is.char, ':---', '---:' ), collapse='|' )
73
  }
74
  else {
75
    dashes <- paste( rep( '---', length( df ) ), collapse = '|' )
76
  }
77
78
  # Create a Markdown version of the data frame.
79
  paste(
80
    paste( names( df ), collapse = '|'), '\n',
81
    dashes, '\n', 
82
    paste(
83
      Reduce( function( x, y ) {
84
          paste( x, format( y, digits = decimals ), sep = '|' )
85
        }, df
86
      ),
87
      collapse = '|\n', sep=''
88
    )
89
  )
90
}
91
192
M README.md
55
A text editor that uses [interpolated strings](https://en.wikipedia.org/wiki/String_interpolation) to reference externally defined values.
66
7
## Requirements
7
## Download
88
9
Download and install the following software packages:
9
Download one of the following editions:
1010
11
* [OpenJDK 14](https://openjdk.java.net)
11
* [Windows](https://gitreleases.dev/gh/DaveJarvis/scrivenvar/latest/scrivenvar.exe)
12
* [Linux](https://gitreleases.dev/gh/DaveJarvis/scrivenvar/latest/scrivenvar.bin)
13
* [Java Archive](https://gitreleases.dev/gh/DaveJarvis/scrivenvar/latest/scrivenvar.jar)
1214
13
## Quick Start
15
## Run
1416
15
Complete the following steps to run the application:
17
Note that the first time the application runs, it will unpack itself into a local directory. Subsequent starts will be faster.
1618
17
1. [Download](https://github.com/DaveJarvis/scrivenvar/releases)
18
`scrivenvar.jar`.
19
1. Double-click `scrivenvar.jar` to start the application.
19
### Windows
2020
21
## Command Line Start
21
On Windows, double-click the application to start. You will have to give the application permission to run.
2222
23
If the quick start fails, run the application as follows:
23
### Linux
2424
25
1. Open a command prompt.
26
1. Change to the download directory containing the archive file.
27
1. Run: `java -jar scrivenvar.jar`
25
On Linux, run `chmod +x scrivenvar.bin` then `./scrivenvar.bin`.
26
27
### Other
28
29
On other platforms, download and install a full version of [OpenJDK 14](https://bell-sw.com/) that includes JavaFX module support, then run:
30
31
``` bash
32
java -jar scrivenvar.jar
33
```
2834
2935
## Features
3036
31
* R integration
32
* User-defined variables, interpolated
37
* User-defined interpolated strings
3338
* Real-time preview with variable substitution
3439
* Auto-complete variable names based on variable values
3540
* XML document transformation using XSLT3 or older
3641
* Platform independent (Windows, Linux, MacOS)
42
* R integration
3743
3844
## Usage
M build.gradle
1
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
2
31
plugins {
42
  id 'application'
...
2523
2624
if (project.hasProperty('targetOs')) {
27
  if ("windows".equals(targetOs)) {
25
  if ("windows" == targetOs) {
2826
    os = ["win"]
29
  }
30
  else {
27
  } else {
3128
    os = [targetOs]
3229
  }
...
9289
  implementation 'org.apache.xmlgraphics:batik-util:1.13'
9390
  implementation 'org.apache.xmlgraphics:batik-xml:1.13'
91
92
  // Spelling implementation
93
  implementation fileTree(include: ['**/*.jar'], dir: 'libs')
9494
9595
  // Misc.
9696
  implementation 'org.ahocorasick:ahocorasick:0.4.0'
9797
  implementation 'org.apache.commons:commons-configuration2:2.7'
9898
  implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3'
99
  implementation 'javax.validation:validation-api:2.0.1.Final'
99100
100101
  def fx = ['controls', 'graphics', 'fxml', 'swing']
A images/architecture/architecture.png
Binary file
A images/architecture/architecture.svg
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<svg
3
   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
   xmlns:cc="http://creativecommons.org/ns#"
5
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
   xmlns:svg="http://www.w3.org/2000/svg"
7
   xmlns="http://www.w3.org/2000/svg"
8
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
   inkscape:export-ydpi="150.0097"
11
   inkscape:export-xdpi="150.0097"
12
   sodipodi:docname="architecture.svg"
13
   viewBox="0 0 764.4414 811.46748"
14
   height="811.46747"
15
   width="764.44141"
16
   id="svg4610"
17
   version="1.2"
18
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
19
  <metadata
20
     id="metadata4616">
21
    <rdf:RDF>
22
      <cc:Work
23
         rdf:about="">
24
        <dc:format>image/svg+xml</dc:format>
25
        <dc:type
26
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
27
        <dc:title />
28
      </cc:Work>
29
    </rdf:RDF>
30
  </metadata>
31
  <defs
32
     id="defs4614">
33
    <marker
34
       inkscape:stockid="Arrow1Mend"
35
       orient="auto"
36
       refY="0"
37
       refX="0"
38
       id="marker10933"
39
       style="overflow:visible"
40
       inkscape:isstock="true">
41
      <path
42
         id="path10931"
43
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
44
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
45
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
46
         inkscape:connector-curvature="0" />
47
    </marker>
48
    <marker
49
       inkscape:stockid="Arrow1Mend"
50
       orient="auto"
51
       refY="0"
52
       refX="0"
53
       id="marker9893"
54
       style="overflow:visible"
55
       inkscape:isstock="true">
56
      <path
57
         id="path9891"
58
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
59
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
60
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
61
         inkscape:connector-curvature="0" />
62
    </marker>
63
    <marker
64
       inkscape:collect="always"
65
       inkscape:isstock="true"
66
       style="overflow:visible"
67
       id="marker9767"
68
       refX="0"
69
       refY="0"
70
       orient="auto"
71
       inkscape:stockid="Arrow1Mend">
72
      <path
73
         inkscape:connector-curvature="0"
74
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
75
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
76
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
77
         id="path9765" />
78
    </marker>
79
    <marker
80
       inkscape:collect="always"
81
       inkscape:stockid="Arrow1Mend"
82
       orient="auto"
83
       refY="0"
84
       refX="0"
85
       id="marker9761"
86
       style="overflow:visible"
87
       inkscape:isstock="true">
88
      <path
89
         id="path9759"
90
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
91
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
92
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
93
         inkscape:connector-curvature="0" />
94
    </marker>
95
    <marker
96
       inkscape:isstock="true"
97
       style="overflow:visible"
98
       id="marker9750"
99
       refX="0"
100
       refY="0"
101
       orient="auto"
102
       inkscape:stockid="Arrow1Mend">
103
      <path
104
         inkscape:connector-curvature="0"
105
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
106
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
107
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
108
         id="path9748" />
109
    </marker>
110
    <marker
111
       inkscape:isstock="true"
112
       style="overflow:visible"
113
       id="marker9715"
114
       refX="0"
115
       refY="0"
116
       orient="auto"
117
       inkscape:stockid="Arrow1Mend">
118
      <path
119
         inkscape:connector-curvature="0"
120
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
121
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
122
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
123
         id="path9713" />
124
    </marker>
125
    <marker
126
       inkscape:collect="always"
127
       inkscape:stockid="Arrow1Mend"
128
       orient="auto"
129
       refY="0"
130
       refX="0"
131
       id="marker9685"
132
       style="overflow:visible"
133
       inkscape:isstock="true">
134
      <path
135
         id="path9683"
136
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
137
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
138
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
139
         inkscape:connector-curvature="0" />
140
    </marker>
141
    <marker
142
       inkscape:collect="always"
143
       inkscape:stockid="Arrow1Mend"
144
       orient="auto"
145
       refY="0"
146
       refX="0"
147
       id="marker9679"
148
       style="overflow:visible"
149
       inkscape:isstock="true">
150
      <path
151
         id="path9677"
152
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
153
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
154
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
155
         inkscape:connector-curvature="0" />
156
    </marker>
157
    <marker
158
       inkscape:collect="always"
159
       inkscape:isstock="true"
160
       style="overflow:visible"
161
       id="marker9640"
162
       refX="0"
163
       refY="0"
164
       orient="auto"
165
       inkscape:stockid="Arrow1Mend">
166
      <path
167
         inkscape:connector-curvature="0"
168
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
169
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
170
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
171
         id="path9638" />
172
    </marker>
173
    <marker
174
       inkscape:collect="always"
175
       inkscape:isstock="true"
176
       style="overflow:visible"
177
       id="marker9513"
178
       refX="0"
179
       refY="0"
180
       orient="auto"
181
       inkscape:stockid="Arrow1Mend">
182
      <path
183
         inkscape:connector-curvature="0"
184
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
185
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
186
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
187
         id="path9511" />
188
    </marker>
189
    <marker
190
       inkscape:stockid="Arrow1Mend"
191
       orient="auto"
192
       refY="0"
193
       refX="0"
194
       id="marker9509"
195
       style="overflow:visible"
196
       inkscape:isstock="true">
197
      <path
198
         id="path9507"
199
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
200
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
201
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
202
         inkscape:connector-curvature="0" />
203
    </marker>
204
    <marker
205
       inkscape:isstock="true"
206
       style="overflow:visible"
207
       id="marker9505"
208
       refX="0"
209
       refY="0"
210
       orient="auto"
211
       inkscape:stockid="Arrow1Mend">
212
      <path
213
         inkscape:connector-curvature="0"
214
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
215
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
216
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
217
         id="path9503" />
218
    </marker>
219
    <marker
220
       inkscape:collect="always"
221
       inkscape:stockid="Arrow1Mend"
222
       orient="auto"
223
       refY="0"
224
       refX="0"
225
       id="marker9479"
226
       style="overflow:visible"
227
       inkscape:isstock="true">
228
      <path
229
         id="path9477"
230
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
231
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
232
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
233
         inkscape:connector-curvature="0" />
234
    </marker>
235
    <clipPath
236
       id="ID000001">
237
      <rect
238
         id="rect6"
239
         height="961.125"
240
         width="1381.6169"
241
         y="-43.688"
242
         x="-62.683998" />
243
    </clipPath>
244
    <filter
245
       id="filter2842"
246
       inkscape:label="Drop Shadow"
247
       style="color-interpolation-filters:sRGB;">
248
      <feFlood
249
         id="feFlood2832"
250
         result="flood"
251
         flood-color="rgb(0,0,0)"
252
         flood-opacity="0.498039" />
253
      <feComposite
254
         id="feComposite2834"
255
         result="composite1"
256
         operator="in"
257
         in2="SourceGraphic"
258
         in="flood" />
259
      <feGaussianBlur
260
         id="feGaussianBlur2836"
261
         result="blur"
262
         stdDeviation="2"
263
         in="composite1" />
264
      <feOffset
265
         id="feOffset2838"
266
         result="offset"
267
         dy="3"
268
         dx="3" />
269
      <feComposite
270
         id="feComposite2840"
271
         result="composite2"
272
         operator="over"
273
         in2="offset"
274
         in="SourceGraphic" />
275
    </filter>
276
    <filter
277
       id="filter2854"
278
       inkscape:label="Drop Shadow"
279
       style="color-interpolation-filters:sRGB;">
280
      <feFlood
281
         id="feFlood2844"
282
         result="flood"
283
         flood-color="rgb(0,0,0)"
284
         flood-opacity="0.498039" />
285
      <feComposite
286
         id="feComposite2846"
287
         result="composite1"
288
         operator="in"
289
         in2="SourceGraphic"
290
         in="flood" />
291
      <feGaussianBlur
292
         id="feGaussianBlur2848"
293
         result="blur"
294
         stdDeviation="2"
295
         in="composite1" />
296
      <feOffset
297
         id="feOffset2850"
298
         result="offset"
299
         dy="3"
300
         dx="3" />
301
      <feComposite
302
         id="feComposite2852"
303
         result="composite2"
304
         operator="over"
305
         in2="offset"
306
         in="SourceGraphic" />
307
    </filter>
308
    <filter
309
       id="filter2866"
310
       inkscape:label="Drop Shadow"
311
       style="color-interpolation-filters:sRGB;">
312
      <feFlood
313
         id="feFlood2856"
314
         result="flood"
315
         flood-color="rgb(0,0,0)"
316
         flood-opacity="0.498039" />
317
      <feComposite
318
         id="feComposite2858"
319
         result="composite1"
320
         operator="in"
321
         in2="SourceGraphic"
322
         in="flood" />
323
      <feGaussianBlur
324
         id="feGaussianBlur2860"
325
         result="blur"
326
         stdDeviation="2"
327
         in="composite1" />
328
      <feOffset
329
         id="feOffset2862"
330
         result="offset"
331
         dy="3"
332
         dx="3" />
333
      <feComposite
334
         id="feComposite2864"
335
         result="composite2"
336
         operator="over"
337
         in2="offset"
338
         in="SourceGraphic" />
339
    </filter>
340
    <filter
341
       id="filter2878"
342
       inkscape:label="Drop Shadow"
343
       style="color-interpolation-filters:sRGB;">
344
      <feFlood
345
         id="feFlood2868"
346
         result="flood"
347
         flood-color="rgb(0,0,0)"
348
         flood-opacity="0.498039" />
349
      <feComposite
350
         id="feComposite2870"
351
         result="composite1"
352
         operator="in"
353
         in2="SourceGraphic"
354
         in="flood" />
355
      <feGaussianBlur
356
         id="feGaussianBlur2872"
357
         result="blur"
358
         stdDeviation="2"
359
         in="composite1" />
360
      <feOffset
361
         id="feOffset2874"
362
         result="offset"
363
         dy="3"
364
         dx="3" />
365
      <feComposite
366
         id="feComposite2876"
367
         result="composite2"
368
         operator="over"
369
         in2="offset"
370
         in="SourceGraphic" />
371
    </filter>
372
    <filter
373
       id="filter2890"
374
       inkscape:label="Drop Shadow"
375
       style="color-interpolation-filters:sRGB;">
376
      <feFlood
377
         id="feFlood2880"
378
         result="flood"
379
         flood-color="rgb(0,0,0)"
380
         flood-opacity="0.498039" />
381
      <feComposite
382
         id="feComposite2882"
383
         result="composite1"
384
         operator="in"
385
         in2="SourceGraphic"
386
         in="flood" />
387
      <feGaussianBlur
388
         id="feGaussianBlur2884"
389
         result="blur"
390
         stdDeviation="2"
391
         in="composite1" />
392
      <feOffset
393
         id="feOffset2886"
394
         result="offset"
395
         dy="3"
396
         dx="3" />
397
      <feComposite
398
         id="feComposite2888"
399
         result="composite2"
400
         operator="over"
401
         in2="offset"
402
         in="SourceGraphic" />
403
    </filter>
404
    <filter
405
       id="filter2902"
406
       inkscape:label="Drop Shadow"
407
       style="color-interpolation-filters:sRGB;">
408
      <feFlood
409
         id="feFlood2892"
410
         result="flood"
411
         flood-color="rgb(0,0,0)"
412
         flood-opacity="0.498039" />
413
      <feComposite
414
         id="feComposite2894"
415
         result="composite1"
416
         operator="in"
417
         in2="SourceGraphic"
418
         in="flood" />
419
      <feGaussianBlur
420
         id="feGaussianBlur2896"
421
         result="blur"
422
         stdDeviation="2"
423
         in="composite1" />
424
      <feOffset
425
         id="feOffset2898"
426
         result="offset"
427
         dy="3"
428
         dx="3" />
429
      <feComposite
430
         id="feComposite2900"
431
         result="composite2"
432
         operator="over"
433
         in2="offset"
434
         in="SourceGraphic" />
435
    </filter>
436
    <filter
437
       id="filter2914"
438
       inkscape:label="Drop Shadow"
439
       style="color-interpolation-filters:sRGB;">
440
      <feFlood
441
         id="feFlood2904"
442
         result="flood"
443
         flood-color="rgb(0,0,0)"
444
         flood-opacity="0.498039" />
445
      <feComposite
446
         id="feComposite2906"
447
         result="composite1"
448
         operator="in"
449
         in2="SourceGraphic"
450
         in="flood" />
451
      <feGaussianBlur
452
         id="feGaussianBlur2908"
453
         result="blur"
454
         stdDeviation="2"
455
         in="composite1" />
456
      <feOffset
457
         id="feOffset2910"
458
         result="offset"
459
         dy="3"
460
         dx="3" />
461
      <feComposite
462
         id="feComposite2912"
463
         result="composite2"
464
         operator="over"
465
         in2="offset"
466
         in="SourceGraphic" />
467
    </filter>
468
    <filter
469
       id="filter2926"
470
       inkscape:label="Drop Shadow"
471
       style="color-interpolation-filters:sRGB;">
472
      <feFlood
473
         id="feFlood2916"
474
         result="flood"
475
         flood-color="rgb(0,0,0)"
476
         flood-opacity="0.498039" />
477
      <feComposite
478
         id="feComposite2918"
479
         result="composite1"
480
         operator="in"
481
         in2="SourceGraphic"
482
         in="flood" />
483
      <feGaussianBlur
484
         id="feGaussianBlur2920"
485
         result="blur"
486
         stdDeviation="2"
487
         in="composite1" />
488
      <feOffset
489
         id="feOffset2922"
490
         result="offset"
491
         dy="3"
492
         dx="3" />
493
      <feComposite
494
         id="feComposite2924"
495
         result="composite2"
496
         operator="over"
497
         in2="offset"
498
         in="SourceGraphic" />
499
    </filter>
500
    <filter
501
       id="filter2938"
502
       inkscape:label="Drop Shadow"
503
       style="color-interpolation-filters:sRGB;">
504
      <feFlood
505
         id="feFlood2928"
506
         result="flood"
507
         flood-color="rgb(0,0,0)"
508
         flood-opacity="0.498039" />
509
      <feComposite
510
         id="feComposite2930"
511
         result="composite1"
512
         operator="in"
513
         in2="SourceGraphic"
514
         in="flood" />
515
      <feGaussianBlur
516
         id="feGaussianBlur2932"
517
         result="blur"
518
         stdDeviation="2"
519
         in="composite1" />
520
      <feOffset
521
         id="feOffset2934"
522
         result="offset"
523
         dy="3"
524
         dx="3" />
525
      <feComposite
526
         id="feComposite2936"
527
         result="composite2"
528
         operator="over"
529
         in2="offset"
530
         in="SourceGraphic" />
531
    </filter>
532
    <filter
533
       id="filter2950"
534
       inkscape:label="Drop Shadow"
535
       style="color-interpolation-filters:sRGB;">
536
      <feFlood
537
         id="feFlood2940"
538
         result="flood"
539
         flood-color="rgb(0,0,0)"
540
         flood-opacity="0.498039" />
541
      <feComposite
542
         id="feComposite2942"
543
         result="composite1"
544
         operator="in"
545
         in2="SourceGraphic"
546
         in="flood" />
547
      <feGaussianBlur
548
         id="feGaussianBlur2944"
549
         result="blur"
550
         stdDeviation="2"
551
         in="composite1" />
552
      <feOffset
553
         id="feOffset2946"
554
         result="offset"
555
         dy="3"
556
         dx="3" />
557
      <feComposite
558
         id="feComposite2948"
559
         result="composite2"
560
         operator="over"
561
         in2="offset"
562
         in="SourceGraphic" />
563
    </filter>
564
    <filter
565
       id="filter2962"
566
       inkscape:label="Drop Shadow"
567
       style="color-interpolation-filters:sRGB;">
568
      <feFlood
569
         id="feFlood2952"
570
         result="flood"
571
         flood-color="rgb(0,0,0)"
572
         flood-opacity="0.498039" />
573
      <feComposite
574
         id="feComposite2954"
575
         result="composite1"
576
         operator="in"
577
         in2="SourceGraphic"
578
         in="flood" />
579
      <feGaussianBlur
580
         id="feGaussianBlur2956"
581
         result="blur"
582
         stdDeviation="2"
583
         in="composite1" />
584
      <feOffset
585
         id="feOffset2958"
586
         result="offset"
587
         dy="3"
588
         dx="3" />
589
      <feComposite
590
         id="feComposite2960"
591
         result="composite2"
592
         operator="over"
593
         in2="offset"
594
         in="SourceGraphic" />
595
    </filter>
596
    <filter
597
       id="filter2974"
598
       inkscape:label="Drop Shadow"
599
       style="color-interpolation-filters:sRGB;">
600
      <feFlood
601
         id="feFlood2964"
602
         result="flood"
603
         flood-color="rgb(0,0,0)"
604
         flood-opacity="0.498039" />
605
      <feComposite
606
         id="feComposite2966"
607
         result="composite1"
608
         operator="in"
609
         in2="SourceGraphic"
610
         in="flood" />
611
      <feGaussianBlur
612
         id="feGaussianBlur2968"
613
         result="blur"
614
         stdDeviation="2"
615
         in="composite1" />
616
      <feOffset
617
         id="feOffset2970"
618
         result="offset"
619
         dy="3"
620
         dx="3" />
621
      <feComposite
622
         id="feComposite2972"
623
         result="composite2"
624
         operator="over"
625
         in2="offset"
626
         in="SourceGraphic" />
627
    </filter>
628
    <filter
629
       id="filter2986"
630
       inkscape:label="Drop Shadow"
631
       style="color-interpolation-filters:sRGB;">
632
      <feFlood
633
         id="feFlood2976"
634
         result="flood"
635
         flood-color="rgb(0,0,0)"
636
         flood-opacity="0.498039" />
637
      <feComposite
638
         id="feComposite2978"
639
         result="composite1"
640
         operator="in"
641
         in2="SourceGraphic"
642
         in="flood" />
643
      <feGaussianBlur
644
         id="feGaussianBlur2980"
645
         result="blur"
646
         stdDeviation="2"
647
         in="composite1" />
648
      <feOffset
649
         id="feOffset2982"
650
         result="offset"
651
         dy="3"
652
         dx="3" />
653
      <feComposite
654
         id="feComposite2984"
655
         result="composite2"
656
         operator="over"
657
         in2="offset"
658
         in="SourceGraphic" />
659
    </filter>
660
  </defs>
661
  <sodipodi:namedview
662
     inkscape:snap-text-baseline="false"
663
     inkscape:document-rotation="0"
664
     fit-margin-bottom="20"
665
     fit-margin-right="20"
666
     fit-margin-left="20"
667
     fit-margin-top="20"
668
     inkscape:current-layer="svg4610"
669
     inkscape:cy="370.55742"
670
     inkscape:cx="398.61418"
671
     inkscape:zoom="1.3753763"
672
     showgrid="false"
673
     id="namedview4612"
674
     inkscape:window-height="1280"
675
     inkscape:window-width="2055"
676
     inkscape:pageshadow="2"
677
     inkscape:pageopacity="1"
678
     guidetolerance="10"
679
     gridtolerance="10"
680
     objecttolerance="10"
681
     borderopacity="1"
682
     bordercolor="#666666"
683
     pagecolor="#ffffff"
684
     inkscape:window-x="215"
685
     inkscape:window-y="26"
686
     inkscape:window-maximized="0" />
687
  <path
688
     sodipodi:nodetypes="ccssssc"
689
     inkscape:connector-curvature="0"
690
     style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#df4d65;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
691
     d="M 53.35547,445.11522 V 790.96744 H 741.0332 c 1.6112,0 2.90821,-1.29701 2.90821,-2.9082 V 448.02342 c 0,-1.6112 -1.297,-2.9082 -2.90821,-2.9082 z"
692
     id="path9961" />
693
  <path
694
     sodipodi:nodetypes="sssccssss"
695
     id="path9940"
696
     d="m 20.5,787.82486 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.35253,0.91797 2.22265,0.91797 H 53.35547 V 445.11522 H 23.64062 c -0.87012,0 -1.65487,0.35019 -2.22265,0.91797 -0.56778,0.56778 -0.91797,1.35254 -0.91797,2.22266 z"
697
     style="fill:#df4d65;fill-opacity:1;fill-rule:nonzero;stroke:#df4d65;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
698
     inkscape:connector-curvature="0" />
699
  <path
700
     sodipodi:nodetypes="sssccssss"
701
     id="path11125"
702
     d="m 20.5,423.31014 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.354494,0.9764 2.22265,0.91797 H 53.35547 V 210.6005 H 23.64062 c -0.87012,0 -1.65487,0.3502 -2.22265,0.918 C 20.85019,212.08629 20.5,212.871 20.5,213.74109 Z"
703
     style="fill:#3e3e3e;fill-opacity:1;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
704
     inkscape:connector-curvature="0" />
705
  <path
706
     sodipodi:nodetypes="ccssssc"
707
     inkscape:connector-curvature="0"
708
     style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
709
     d="m 53.35547,210.6005 v 215.85222 h 687.67774 c 1.6112,0 2.9082,-1.29701 2.9082,-2.9082 V 213.5087 c 0,-1.6112 -1.29701,-2.90352 -2.9082,-2.9082 z"
710
     id="path11123" />
711
  <path
712
     id="path6150"
713
     d="m 557.756,222.53493 c -0.87012,0 -1.65683,0.35019 -2.22461,0.91797 -0.56778,0.56778 -0.91797,1.35253 -0.91797,2.22265 v 29.71485 h 165.6211 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
714
     style="fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2926)"
715
     inkscape:connector-curvature="0"
716
     sodipodi:nodetypes="sssccssss" />
717
  <path
718
     sodipodi:nodetypes="ccssssc"
719
     id="path6134"
720
     d="m 720.75716,255.39041 h -165.6211 v 152.63392 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z"
721
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2926)"
722
     inkscape:connector-curvature="0" />
723
  <path
724
     id="path6082"
725
     d="m 317.13559,222.53494 c -0.87011,0 -1.65683,0.35019 -2.2246,0.91797 -0.56779,0.56778 -0.91798,1.35253 -0.91798,2.22265 v 29.71485 h 165.62111 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91798,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
726
     style="fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2938)"
727
     inkscape:connector-curvature="0"
728
     sodipodi:nodetypes="sssccssss" />
729
  <path
730
     sodipodi:nodetypes="ccssssc"
731
     id="path6080"
732
     d="M 479.61412,255.39041 H 313.99301 v 152.63392 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.90821,-1.29701 2.90821,-2.90821 z"
733
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2938)"
734
     inkscape:connector-curvature="0" />
735
  <path
736
     id="path10980"
737
     d="M 53.35547,20.500012 V 188.35224 h 687.67774 c 1.6112,0 2.9082,-1.29701 2.9082,-2.9082 V 23.408212 c 0,-1.6112 -1.29701,-2.912886 -2.9082,-2.9082 z"
738
     style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
739
     inkscape:connector-curvature="0"
740
     sodipodi:nodetypes="ccssssc" />
741
  <path
742
     inkscape:connector-curvature="0"
743
     style="fill:#3e3e3e;fill-opacity:1;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
744
     d="m 20.5,185.20966 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.35253,0.91797 2.22265,0.91797 H 53.35547 V 20.500012 H 23.64062 c -0.87012,0 -1.65487,0.350201 -2.22265,0.918 -0.56778,0.5678 -0.91797,1.3525 -0.91797,2.2226 z"
745
     id="path10982"
746
     sodipodi:nodetypes="sssccssss" />
747
  <path
748
     sodipodi:nodetypes="sssccssss"
749
     inkscape:connector-curvature="0"
750
     style="fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2962)"
751
     d="m 557.75599,36.704447 c -0.87012,0 -1.65683,0.35019 -2.22461,0.91797 -0.56778,0.56778 -0.91797,1.35253 -0.91797,2.22265 v 29.71485 h 165.6211 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
752
     id="path4857" />
753
  <path
754
     inkscape:connector-curvature="0"
755
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2962)"
756
     d="M 720.23451,69.559917 H 554.61341 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z"
757
     id="path4853" />
758
  <path
759
     sodipodi:nodetypes="sssccssss"
760
     inkscape:connector-curvature="0"
761
     style="fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2974)"
762
     d="m 317.13558,36.704447 c -0.87011,0 -1.65683,0.35019 -2.2246,0.91797 -0.56779,0.56778 -0.91798,1.35253 -0.91798,2.22265 v 29.71485 h 165.62111 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91798,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
763
     id="path5726" />
764
  <path
765
     inkscape:connector-curvature="0"
766
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2974)"
767
     d="M 479.61411,69.559917 H 313.993 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 H 476.7059 c 1.6112,0 2.90821,-1.29701 2.90821,-2.90821 z"
768
     id="path5724" />
769
  <path
770
     id="path4721"
771
     d="m 235.85308,44.704447 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z"
772
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
773
     inkscape:connector-curvature="0" />
774
  <path
775
     sodipodi:nodetypes="sssccssss"
776
     id="path4719"
777
     d="m 76.515197,36.704447 c -0.870125,0 -1.656831,0.35019 -2.22461,0.91797 -0.567778,0.56778 -0.917968,1.35253 -0.917968,2.22265 v 29.71485 H 238.99371 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
778
     style="fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2986)"
779
     inkscape:connector-curvature="0" />
780
  <path
781
     id="path4723"
782
     d="M 238.99372,69.559917 H 73.372613 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 H 236.08552 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z"
783
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2986)"
784
     inkscape:connector-curvature="0" />
785
  <path
786
     id="rect4622"
787
     d="m 76.280822,44.704447 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z"
788
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
789
     inkscape:connector-curvature="0" />
790
  <path
791
     sodipodi:nodetypes="cc"
792
     inkscape:connector-curvature="0"
793
     id="path9889"
794
     d="m 397.61301,500.62068 -0.50618,32.59418"
795
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:1.9694221;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9893)" />
796
  <path
797
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9715)"
798
     d="m 554.61351,648.83688 -69.6817,47.69253"
799
     id="path9711"
800
     inkscape:connector-curvature="0"
801
     sodipodi:nodetypes="cc" />
802
  <path
803
     sodipodi:nodetypes="cc"
804
     inkscape:connector-curvature="0"
805
     id="path9675"
806
     d="M 554.61351,567.95047 484.93181,615.643"
807
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9679)" />
808
  <rect
809
     ry="3.9839513"
810
     rx="3.9205718"
811
     y="537.09552"
812
     x="554.61353"
813
     height="32.855"
814
     width="165.621"
815
     id="rect9618"
816
     style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2842)" />
817
  <rect
818
     ry="3.9839513"
819
     rx="3.9205718"
820
     y="537.09552"
821
     x="73.372665"
822
     height="32.855"
823
     width="165.621"
824
     id="rect9614"
825
     style="opacity:1;fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2914)" />
826
  <path
827
     inkscape:connector-curvature="0"
828
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
829
     d="m 235.85308,545.09525 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z"
830
     id="path9323" />
831
  <path
832
     inkscape:connector-curvature="0"
833
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
834
     d="m 76.280823,545.09525 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z"
835
     id="path9327" />
836
  <rect
837
     style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2902)"
838
     id="rect9616"
839
     width="165.621"
840
     height="32.855"
841
     x="313.99307"
842
     y="537.09552"
843
     rx="3.9205718"
844
     ry="3.9839513" />
845
  <path
846
     sodipodi:nodetypes="cc"
847
     inkscape:connector-curvature="0"
848
     id="path9491"
849
     d="m 240.99257,554.11276 65.23376,-1.01307"
850
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9513)" />
851
  <path
852
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9640)"
853
     d="m 481.61298,554.11276 65.23376,-1.01307"
854
     id="path9501"
855
     inkscape:connector-curvature="0"
856
     sodipodi:nodetypes="cc" />
857
  <rect
858
     ry="3.9839513"
859
     rx="3.9205718"
860
     y="617.79578"
861
     x="313.99307"
862
     height="32.855"
863
     width="165.621"
864
     id="rect9620"
865
     style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2890)" />
866
  <path
867
     sodipodi:nodetypes="cc"
868
     inkscape:connector-curvature="0"
869
     id="path9681"
870
     d="m 481.61298,634.81299 65.23376,-1.01307"
871
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9685)" />
872
  <rect
873
     style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2854)"
874
     id="rect9687"
875
     width="165.621"
876
     height="32.855"
877
     x="554.61353"
878
     y="617.79578"
879
     rx="3.9205718"
880
     ry="3.9839513" />
881
  <path
882
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9750)"
883
     d="m 481.61298,715.51321 65.23376,-1.01307"
884
     id="path9734"
885
     inkscape:connector-curvature="0"
886
     sodipodi:nodetypes="cc" />
887
  <rect
888
     ry="3.9839513"
889
     rx="3.9205718"
890
     y="698.49591"
891
     x="554.61353"
892
     height="32.855"
893
     width="165.621"
894
     id="rect9736"
895
     style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2866)" />
896
  <path
897
     id="path9830"
898
     d="m 356.40451,489.45323 c -0.80426,0 -1.45167,0.64741 -1.45167,1.45166 v 0.11602 c 0,-0.43433 0.1748,-0.82605 0.45822,-1.10946 0.28341,-0.28342 0.6761,-0.45822 1.11043,-0.45822 z"
899
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10902636;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
900
     inkscape:connector-curvature="0" />
901
  <rect
902
     style="opacity:1;fill:#ffb73a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.9391377;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
903
     id="rect9826"
904
     width="120.98324"
905
     height="24"
906
     x="336.82672"
907
     y="477.86002"
908
     rx="2.8639088"
909
     ry="2.9102066" />
910
  <path
911
     id="path10514"
912
     d="m 235.85301,637.23875 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z"
913
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
914
     inkscape:connector-curvature="0" />
915
  <rect
916
     style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2878)"
917
     id="rect9717"
918
     width="165.621"
919
     height="32.855"
920
     x="313.99307"
921
     y="698.49591"
922
     rx="3.9205718"
923
     ry="3.9839513" />
924
  <path
925
     id="path10537"
926
     d="M 238.99366,636.97465 H 73.372671 V 729.175 c 0,1.2055 0.970418,2.17592 2.175911,2.17592 H 236.81776 c 1.20549,0 2.1759,-0.97042 2.1759,-2.17592 z"
927
     style="opacity:1;fill:#333333;fill-opacity:0.93333333;fill-rule:nonzero;stroke:none;stroke-width:0.16342013;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
928
     inkscape:connector-curvature="0"
929
     sodipodi:nodetypes="ccssssc" />
930
  <path
931
     sodipodi:nodetypes="sssccssss"
932
     id="path10516"
933
     d="m 75.723937,612.39226 c -0.651025,0 -1.239637,0.26201 -1.664447,0.68682 -0.424811,0.42482 -0.686822,1.01196 -0.686822,1.66299 v 22.23258 H 238.99366 v -22.23258 c 0,-0.65103 -0.26201,-1.23817 -0.68682,-1.66299 -0.42481,-0.42481 -1.01197,-0.68682 -1.66299,-0.68682 z"
934
     style="opacity:1;fill:#ffb73a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.16342013;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
935
     inkscape:connector-curvature="0" />
936
  <path
937
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker10933)"
938
     d="m 240.99257,715.51318 65.23376,-1.01307"
939
     id="path10929"
940
     inkscape:connector-curvature="0"
941
     sodipodi:nodetypes="cc" />
942
  <path
943
     style="fill:#df4d65;fill-opacity:1;fill-rule:evenodd;stroke-width:0.05250736"
944
     d="m 399.47357,99.155037 -0.12716,0.0127 -0.12716,0.0141 -0.12574,0.0141 -0.12716,0.0155 -0.61883,0.967813 -0.29106,0.9325 -0.17943,0.91977 -0.1215,1.09073 -0.30943,0.0636 -0.308,0.0678 -0.308,0.0735 -0.30801,0.0763 -0.58069,-0.93108 -0.5609,-0.75023 -0.66687,-0.71349 -0.97912,-0.6033 -0.12009,0.0409 -0.1201,0.0424 -0.12009,0.0424 -0.12009,0.0424 -0.38854,1.08085 -0.0763,0.97487 0.0297,0.93673 0.12575,1.08932 -0.28823,0.1314 -0.28681,0.13563 -0.28398,0.13987 -0.28117,0.14271 -0.77425,-0.77849 -0.7149,-0.60612 -0.80816,-0.54819 -1.0879,-0.37017 -0.10879,0.0678 -0.10738,0.0679 -0.10738,0.0678 -0.10738,0.0678 -0.13846,1.14018 0.14129,0.9664 0.23735,0.90706 0.36594,1.03563 -0.25149,0.19215 -0.24865,0.19497 -0.24585,0.19922 -0.24442,0.20204 -0.92825,-0.58634 -0.83077,-0.43234 -0.90988,-0.35463 -1.14301,-0.11868 -0.0904,0.0904 -0.0904,0.089 -0.089,0.0904 -0.0904,0.0904 0.11868,1.14301 0.35462,0.90988 0.43234,0.83218 0.58634,0.92684 -0.20204,0.24442 -0.19921,0.24584 -0.19498,0.24867 -0.19215,0.25148 -1.03563,-0.36593 -0.90705,-0.23736 -0.96641,-0.14128 -1.14018,0.13845 -0.0678,0.10738 -0.0678,0.10738 -0.0678,0.10738 -0.0678,0.10879 0.37016,1.0879 0.5482,0.80816 0.60612,0.71491 0.77848,0.77425 -0.1427,0.28117 -0.13987,0.28398 -0.13564,0.28681 -0.13139,0.28823 -1.09073,-0.12575 -0.93532,-0.0297 -0.97487,0.0763 -1.08084,0.38854 -0.0424,0.12009 -0.0424,0.12009 -0.0424,0.1201 -0.0409,0.12009 0.6033,0.97912 0.7135,0.66686 0.75023,0.56091 0.93107,0.58069 -0.0763,0.30801 -0.0735,0.308 -0.0678,0.308 -0.0636,0.30942 -1.09073,0.1215 -0.91977,0.17944 -0.9325,0.29105 -0.96781,0.61883 -0.0156,0.12717 -0.0141,0.12574 -0.0141,0.12716 -0.0127,0.12716 0.80533,0.81804 0.84348,0.49168 0.85619,0.38006 1.03704,0.36028 -0.006,0.31648 -0.003,0.31648 0.003,0.31648 0.006,0.31648 -1.03704,0.35887 -0.85619,0.38006 -0.84348,0.49168 -0.80533,0.81946 0.0127,0.12716 0.0141,0.12574 0.0141,0.12716 0.0156,0.12574 0.96781,0.62026 0.9325,0.29104 0.91977,0.17944 1.09073,0.12009 0.0636,0.30942 0.0678,0.30941 0.0735,0.30801 0.0763,0.30659 -0.93107,0.5821 -0.75023,0.56091 -0.7135,0.66687 -0.6033,0.97771 0.0409,0.12008 0.0424,0.12151 0.0424,0.1201 0.0424,0.11868 1.08084,0.38995 0.97487,0.0763 0.93532,-0.0297 1.09073,-0.12574 0.13139,0.28822 0.13564,0.2854 0.13987,0.28399 0.1427,0.28257 -0.77848,0.77425 -0.60612,0.7135 -0.5482,0.80957 -0.37016,1.08791 0.0678,0.10737 0.0678,0.10879 0.0678,0.10738 0.0678,0.10738 1.14018,0.13846 0.96641,-0.1427 0.90705,-0.23736 1.03563,-0.36452 0.19215,0.25149 0.19498,0.24866 0.19921,0.24584 0.20204,0.24302 -0.58634,0.92825 -0.43234,0.83076 -0.35462,0.9113 -0.11868,1.14159 0.0904,0.0918 0.089,0.089 0.0904,0.0904 0.0904,0.089 1.14301,-0.11868 0.90988,-0.35321 0.83077,-0.43375 0.92825,-0.58493 0.24442,0.20204 0.24585,0.19921 0.24865,0.19497 0.25149,0.19216 -0.36594,1.03563 -0.23735,0.90564 -0.14129,0.9664 0.13846,1.14018 0.10738,0.0692 0.10738,0.0678 0.10738,0.0678 0.10879,0.0664 1.0879,-0.37017 0.80816,-0.54677 0.7149,-0.60754 0.77425,-0.77708 0.28117,0.14271 0.28398,0.13987 0.28681,0.13422 0.28823,0.13139 -0.12575,1.09074 -0.0297,0.93673 0.0763,0.97346 0.38854,1.08084 0.12009,0.0438 0.12009,0.0424 0.1201,0.041 0.12009,0.0409 0.97912,-0.60188 0.66687,-0.71349 0.5609,-0.75165 0.58069,-0.93108 0.30801,0.0777 0.308,0.072 0.308,0.0692 0.30943,0.0636 0.1215,1.09073 0.17943,0.91978 0.29106,0.93249 0.61883,0.9664 0.12716,0.0156 0.12574,0.0141 0.12716,0.0141 0.12716,0.0141 0.81806,-0.80533 0.49167,-0.8449 0.38006,-0.85619 0.36028,-1.03704 0.31648,0.007 0.31648,0.003 0.31649,-0.003 0.31648,-0.007 0.36028,1.03704 0.37865,0.85619 0.49167,0.8449 0.81947,0.80533 0.12715,-0.0141 0.12574,-0.0141 0.12717,-0.0141 0.12574,-0.0156 0.62025,-0.9664 0.29104,-0.93249 0.17944,-0.91978 0.12009,-1.09073 0.30942,-0.0636 0.30942,-0.0692 0.30799,-0.072 0.3066,-0.0777 0.58211,0.93108 0.5609,0.75165 0.66687,0.71349 0.97771,0.60188 0.12009,-0.0409 0.1215,-0.041 0.1201,-0.0424 0.11868,-0.0438 0.38995,-1.08084 0.0763,-0.97346 -0.0297,-0.93673 -0.12574,-1.09074 0.28822,-0.13139 0.2854,-0.13422 0.28398,-0.13987 0.28258,-0.14271 0.77424,0.77708 0.7135,0.60754 0.80957,0.54677 1.08791,0.37017 0.10737,-0.0664 0.10879,-0.0678 0.10738,-0.0678 0.10738,-0.0692 0.13847,-1.14018 -0.14271,-0.9664 -0.23737,-0.90564 -0.36452,-1.03563 0.25149,-0.19216 0.24866,-0.19497 0.24585,-0.19921 0.24301,-0.20204 0.92825,0.58493 0.83077,0.43375 0.91129,0.35321 1.1416,0.11868 0.0904,-0.089 0.0904,-0.0904 0.0904,-0.089 0.089,-0.0918 -0.11868,-1.14159 -0.35321,-0.9113 -0.43375,-0.83076 -0.58492,-0.92825 0.20203,-0.24302 0.19921,-0.24584 0.19498,-0.24866 0.19215,-0.25149 1.03563,0.36452 0.90564,0.23736 0.9664,0.1427 1.14018,-0.13846 0.0692,-0.10738 0.0678,-0.10738 0.0678,-0.10879 0.0664,-0.10737 -0.37017,-1.08791 -0.54677,-0.80957 -0.60754,-0.7135 -0.77706,-0.77425 0.1427,-0.28257 0.13986,-0.28399 0.13423,-0.2854 0.13139,-0.28822 1.09073,0.12574 0.93674,0.0297 0.97345,-0.0763 1.08085,-0.38995 0.0438,-0.11868 0.0424,-0.1201 0.0409,-0.12151 0.041,-0.12008 -0.6019,-0.97771 -0.71349,-0.66687 -0.75164,-0.56091 -0.93108,-0.5821 0.0777,-0.30659 0.072,-0.30801 0.0692,-0.30941 0.0636,-0.30942 1.09073,-0.12009 0.91978,-0.17944 0.93249,-0.29104 0.9664,-0.62026 0.0155,-0.12574 0.0141,-0.12716 0.0141,-0.12574 0.0141,-0.12716 -0.80533,-0.81946 -0.8449,-0.49168 -0.85619,-0.38006 -1.03704,-0.35887 0.007,-0.31648 0.003,-0.31648 -0.003,-0.31648 -0.007,-0.31648 1.03704,-0.36028 0.85619,-0.38006 0.8449,-0.49168 0.80533,-0.81804 -0.0141,-0.12716 -0.0141,-0.12716 -0.0141,-0.12574 -0.0155,-0.12717 -0.9664,-0.61883 -0.93249,-0.29105 -0.91978,-0.17944 -1.09073,-0.1215 -0.0636,-0.30942 -0.0692,-0.308 -0.072,-0.308 -0.0777,-0.30801 0.93108,-0.58069 0.75164,-0.56091 0.71349,-0.66686 0.6019,-0.97912 -0.041,-0.12009 -0.0409,-0.1201 -0.0424,-0.12009 -0.0438,-0.12009 -1.08085,-0.38854 -0.97345,-0.0763 -0.93674,0.0297 -1.09073,0.12575 -0.13139,-0.28823 -0.13423,-0.28681 -0.13986,-0.28398 -0.1427,-0.28117 0.77706,-0.77425 0.60754,-0.71491 0.54677,-0.80816 0.37017,-1.0879 -0.0664,-0.10879 -0.0678,-0.10738 -0.0678,-0.10738 -0.0692,-0.10738 -1.14018,-0.13845 -0.9664,0.14128 -0.90564,0.23736 -1.03563,0.36593 -0.19215,-0.25148 -0.19498,-0.24867 -0.19921,-0.24584 -0.20203,-0.24442 0.58492,-0.92684 0.43375,-0.83218 0.35321,-0.90988 0.11868,-1.14301 -0.089,-0.0904 -0.0904,-0.0904 -0.0904,-0.089 -0.0904,-0.0904 -1.1416,0.11868 -0.91129,0.35463 -0.83077,0.43234 -0.92825,0.58634 -0.24301,-0.20204 -0.24585,-0.19922 -0.24866,-0.19497 -0.25149,-0.19215 0.36452,-1.03563 0.23737,-0.90706 0.14271,-0.9664 -0.13847,-1.14018 -0.10738,-0.0678 -0.10738,-0.0678 -0.10879,-0.0679 -0.10737,-0.0678 -1.08791,0.37017 -0.80957,0.54819 -0.7135,0.60612 -0.77424,0.77849 -0.28258,-0.14271 -0.28398,-0.13987 -0.2854,-0.13563 -0.28822,-0.1314 0.12574,-1.08932 0.0297,-0.93673 -0.0763,-0.97487 -0.38995,-1.08085 -0.11868,-0.0424 -0.1201,-0.0424 -0.1215,-0.0424 -0.12009,-0.0409 -0.97771,0.6033 -0.66687,0.71349 -0.5609,0.75023 -0.58211,0.93108 -0.3066,-0.0763 -0.30799,-0.0735 -0.30942,-0.0678 -0.30942,-0.0636 -0.12009,-1.09073 -0.17944,-0.91977 -0.29104,-0.9325 -0.62025,-0.967813 -0.12574,-0.0155 -0.12717,-0.0141 -0.12574,-0.0141 -0.12715,-0.0127 -0.81947,0.80533 -0.49167,0.843483 -0.37865,0.8562 -0.36028,1.03704 -0.31648,-0.006 -0.31649,-0.003 -0.31648,0.003 -0.31648,0.006 -0.36028,-1.03704 -0.38006,-0.8562 -0.49167,-0.843483 z m 2.68302,20.688573 a 5.3990039,5.3990039 0 0 1 5.39856,5.39997 5.3990039,5.3990039 0 0 1 -5.39856,5.39855 5.3990039,5.3990039 0 0 1 -5.39996,-5.39855 5.3990039,5.3990039 0 0 1 5.39996,-5.39997 z"
945
     id="path5693"
946
     inkscape:connector-curvature="0" />
947
  <path
948
     inkscape:connector-curvature="0"
949
     d="m 380.9529,101.31918 a 4.37599,4.37599 0 0 1 -4.37599,4.37599 4.37599,4.37599 0 0 1 -4.37599,-4.37599 4.37599,4.37599 0 0 1 4.37599,-4.375983 4.37599,4.37599 0 0 1 4.37599,4.375983 z m 4.63493,-1.27213 c -0.32212,-0.118873 -0.95326,0.0926 -0.92258,-0.401293 -0.13877,-0.39635 -0.21401,-0.74537 0.27363,-0.88946 0.78055,-0.47633 1.45123,-1.16128 1.74461,-2.04171 0.15411,-0.39145 -0.3432,-0.48754 -0.63657,-0.53536 -0.91614,-0.25589 -1.86519,0.0578 -2.73328,0.35995 -0.11023,-0.31345 -0.69059,-0.56868 -0.47901,-0.88657 0.56405,-0.84324 0.99162,-1.8335 0.85012,-2.86709 -0.031,-0.41955 -0.52073,-0.29038 -0.8058,-0.20618 -0.93646,0.16692 -1.65537,0.86143 -2.30642,1.51029 -0.23528,-0.23464 -0.86896,-0.21274 -0.81624,-0.59097 0.14241,-1.00446 0.0978,-2.08217 -0.47806,-2.95201 -0.20995,-0.36459 -0.59514,-0.0357 -0.81544,0.16385 -0.7713,0.55671 -1.11768,1.49434 -1.42274,2.36143 -0.3138,-0.10927 -0.87519,0.18536 -0.99181,-0.17826 -0.30757,-0.96675 -0.81528,-1.91841 -1.71153,-2.45229 -0.34734,-0.23737 -0.55176,0.22606 -0.66364,0.50149 -0.45331,0.83622 -0.35865,1.83127 -0.25723,2.74482 -0.33012,0.0378 -0.70817,0.54672 -0.97098,0.26977 -0.69651,-0.73763 -1.56687,-1.37476 -2.60601,-1.46682 -0.41593,-0.0631 -0.39904,0.443 -0.38034,0.73971 -0.0456,0.95009 0.47143,1.80554 0.95918,2.58463 -0.28104,0.17725 -0.40076,0.79984 -0.75777,0.6643 -0.94759,-0.36236 -2.00818,-0.55864 -2.98437,-0.1908 -0.40215,0.12357 -0.16723,0.57227 -0.0217,0.83147 0.37114,0.8758 1.2081,1.42221 1.98561,1.91248 -0.17632,0.28163 -0.0141,0.89453 -0.39449,0.92732 -1.01097,0.0847 -2.05173,0.367983 -2.77166,1.122963 -0.3087,0.2858 0.0976,0.58816 0.34122,0.75853 0.71439,0.62804 1.70558,0.75716 2.6188,0.8616 -0.0367,0.33025 0.37548,0.81204 0.0469,1.00665 -0.87415,0.51488 -1.68886,1.22174 -2.00994,2.2143 -0.15411,0.39144 0.3432,0.48753 0.63657,0.53535 0.91612,0.25589 1.86516,-0.0578 2.73323,-0.35995 0.11029,0.31341 0.69063,0.56869 0.47905,0.88657 -0.56412,0.8432 -0.99155,1.83352 -0.85015,2.86709 0.031,0.41952 0.52075,0.2904 0.80584,0.20618 0.93644,-0.16692 1.65537,-0.86139 2.30637,-1.51029 0.2353,0.23464 0.86901,0.21272 0.81629,0.59098 -0.14241,1.00446 -0.0978,2.08215 0.47802,2.95202 0.20997,0.36455 0.59517,0.0357 0.81548,-0.16387 0.77125,-0.55674 1.11768,-1.49435 1.42274,-2.36142 0.31379,0.10926 0.8752,-0.18537 0.99181,0.17824 0.30754,0.96678 0.81527,1.91842 1.71153,2.45229 0.34733,0.23738 0.55172,-0.22608 0.66362,-0.50146 0.45335,-0.83621 0.35866,-1.83128 0.25725,-2.74484 0.33011,-0.0378 0.70812,-0.54672 0.97093,-0.26977 0.69656,0.7376 1.5669,1.37477 2.60606,1.46683 0.41593,0.0632 0.39897,-0.44304 0.38032,-0.73972 0.0457,-0.95011 -0.4715,-1.8055 -0.95916,-2.58463 0.28105,-0.17722 0.40074,-0.79983 0.75772,-0.6643 0.94761,0.36234 2.00821,0.55865 2.98442,0.1908 0.40215,-0.12357 0.16723,-0.57228 0.0217,-0.83146 -0.37116,-0.87579 -1.20814,-1.42218 -1.98561,-1.91249 0.17632,-0.28163 0.0141,-0.89453 0.39449,-0.92732 1.01097,-0.0847 2.05173,-0.36799 2.77166,-1.12295 0.3087,-0.28581 -0.0976,-0.58817 -0.34122,-0.75854 -0.47483,-0.43652 -1.13407,-0.61787 -1.75144,-0.75008 z"
950
     style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke-width:0.04014921"
951
     id="path5687" />
952
  <path
953
     style="fill:#05556e;fill-opacity:1;stroke-width:0.11881336"
954
     id="path4816"
955
     d="m 619.92265,90.37586 h 2.72717 v 2.71445 h 2.51374 v -2.71445 h 2.72716 v 8.21477 h -2.72716 v -2.73825 h -2.49003 v 2.73825 h -2.75088 m 11.57268,-5.47651 h -2.40702 v -2.73826 h 7.55307 v 2.73826 h -2.41888 v 5.47651 h -2.72717 m 6.34363,-8.21477 h 2.8576 l 1.75487,2.89303 1.75487,-2.89303 h 2.8576 v 8.21477 h -2.72717 v -4.07167 l -1.90901,2.95256 -1.90902,-2.95256 v 4.07167 h -2.67974 m 10.57667,-8.21477 h 2.72717 v 5.50033 h 3.86546 v 2.71444 h -6.59263"
956
     inkscape:connector-curvature="0" />
957
  <path
958
     id="path4818"
959
     d="m 619.82779,146.45062 -3.91289,-44.09786 h 43.01811 l -3.91289,44.07405 -17.63174,4.90505"
960
     inkscape:connector-curvature="0"
961
     style="fill:#e44d26;stroke-width:0.11881336" />
962
  <path
963
     id="path4820"
964
     d="m 637.42396,147.58164 v -41.60962 h 17.5843 l -3.3556,37.62129"
965
     inkscape:connector-curvature="0"
966
     style="fill:#f16529;stroke-width:0.11881336" />
967
  <path
968
     id="path4822"
969
     d="m 623.90669,111.3652 h 13.51727 v 5.40508 h -7.61236 l 0.498,5.53605 h 7.11436 v 5.39318 h -12.04697 m 0.23714,2.71444 h 5.40691 l 0.37943,4.32169 6.02349,1.61914 v 5.64319 L 626.373,138.90255"
970
     inkscape:connector-curvature="0"
971
     style="fill:#ebebeb;stroke-width:0.11881336" />
972
  <path
973
     id="path4824"
974
     d="m 650.89379,111.3652 h -13.49355 v 5.40508 h 12.99555 m -0.48615,5.53605 h -12.5094 v 5.40508 h 6.64006 l -0.62843,7.02423 -6.01163,1.61914 v 5.61938 l 11.02724,-3.07161"
975
     inkscape:connector-curvature="0"
976
     style="fill:#ffffff;stroke-width:0.11881336" />
977
  <path
978
     sodipodi:nodetypes="cc"
979
     inkscape:connector-curvature="0"
980
     id="path5804"
981
     d="m 240.99252,105.07517 65.2338,-1.01308"
982
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9479)" />
983
  <path
984
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9505)"
985
     d="m 481.61302,105.07517 65.2337,-1.01308"
986
     id="path9497"
987
     inkscape:connector-curvature="0"
988
     sodipodi:nodetypes="cc" />
989
  <path
990
     inkscape:connector-curvature="0"
991
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
992
     d="m 235.85308,230.53494 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z"
993
     id="path6102" />
994
  <path
995
     inkscape:connector-curvature="0"
996
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
997
     d="m 76.280823,230.53494 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z"
998
     id="path6106" />
999
  <path
1000
     inkscape:connector-curvature="0"
1001
     style="fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2950)"
1002
     d="m 76.515198,222.53494 c -0.870125,0 -1.656831,0.35019 -2.22461,0.91797 -0.567778,0.56778 -0.917968,1.35253 -0.917968,2.22265 v 29.71485 h 165.62109 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
1003
     id="path6104"
1004
     sodipodi:nodetypes="sssccssss" />
1005
  <path
1006
     sodipodi:nodetypes="ccssssc"
1007
     inkscape:connector-curvature="0"
1008
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2950)"
1009
     d="M 238.99371,255.39041 H 73.37262 v 152.63392 c 0,1.6112 1.297008,2.90821 2.908203,2.90821 H 236.08551 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z"
1010
     id="path6100" />
1011
  <path
1012
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9509)"
1013
     d="m 240.99257,328.95043 65.23376,-1.01307"
1014
     id="path9485"
1015
     inkscape:connector-curvature="0"
1016
     sodipodi:nodetypes="cc" />
1017
  <path
1018
     sodipodi:nodetypes="cc"
1019
     inkscape:connector-curvature="0"
1020
     id="path9757"
1021
     d="m 481.61298,300.08996 65.23376,-1.01307"
1022
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9761)" />
1023
  <path
1024
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9767)"
1025
     d="M 552.61456,372.04139 487.3808,371.02832"
1026
     id="path9763"
1027
     inkscape:connector-curvature="0"
1028
     sodipodi:nodetypes="cc" />
1029
  <text
1030
     id="text2269"
1031
     y="62.149761"
1032
     x="115.43707"
1033
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1034
     xml:space="preserve"><tspan
1035
       y="62.149761"
1036
       x="115.43707"
1037
       id="tspan2267"
1038
       sodipodi:role="line">Text Edit</tspan></text>
1039
  <text
1040
     transform="rotate(-90)"
1041
     id="text2273"
1042
     y="43.507812"
1043
     x="-132.24059"
1044
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1045
     xml:space="preserve"><tspan
1046
       y="43.507812"
1047
       x="-132.24059"
1048
       id="tspan2271"
1049
       sodipodi:role="line">Today</tspan></text>
1050
  <text
1051
     id="text2277"
1052
     y="61.540386"
1053
     x="358.88168"
1054
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1055
     xml:space="preserve"><tspan
1056
       y="61.540386"
1057
       x="358.88168"
1058
       id="tspan2275"
1059
       sodipodi:role="line">Process</tspan></text>
1060
  <text
1061
     id="text2281"
1062
     y="59.34898"
1063
     x="605.30872"
1064
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1065
     xml:space="preserve"><tspan
1066
       y="59.34898"
1067
       x="605.30872"
1068
       id="tspan2279"
1069
       sodipodi:role="line">Output</tspan></text>
1070
  <text
1071
     id="text2285"
1072
     y="245.17946"
1073
     x="605.30872"
1074
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1075
     xml:space="preserve"><tspan
1076
       y="245.17946"
1077
       x="605.30872"
1078
       id="tspan2283"
1079
       sodipodi:role="line">Output</tspan></text>
1080
  <text
1081
     id="text2289"
1082
     y="247.37088"
1083
     x="358.88168"
1084
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1085
     xml:space="preserve"><tspan
1086
       y="247.37088"
1087
       x="358.88168"
1088
       id="tspan2287"
1089
       sodipodi:role="line">Process</tspan></text>
1090
  <text
1091
     id="text2293"
1092
     y="247.98026"
1093
     x="115.43707"
1094
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1095
     xml:space="preserve"><tspan
1096
       y="247.98026"
1097
       x="115.43707"
1098
       id="tspan2291"
1099
       sodipodi:role="line">Text Edit</tspan></text>
1100
  <text
1101
     transform="rotate(-90)"
1102
     id="text2297"
1103
     y="43.630859"
1104
     x="-363.15442"
1105
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1106
     xml:space="preserve"><tspan
1107
       y="43.630859"
1108
       x="-363.15442"
1109
       id="tspan2295"
1110
       sodipodi:role="line">Proposed</tspan></text>
1111
  <text
1112
     id="text2301"
1113
     y="314.01108"
1114
     x="98.034729"
1115
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1116
     xml:space="preserve"><tspan
1117
       id="tspan2299"
1118
       sodipodi:role="line"
1119
       x="98.034729"
1120
       y="314.01108">R Markdown</tspan></text>
1121
  <text
1122
     id="text2305"
1123
     y="285.84311"
1124
     x="107.43903"
1125
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1126
     xml:space="preserve"><tspan
1127
       id="tspan2303"
1128
       sodipodi:role="line"
1129
       x="107.43903"
1130
       y="285.84311">Markdown</tspan></text>
1131
  <text
1132
     id="text2309"
1133
     y="342.91147"
1134
     x="134.3277"
1135
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1136
     xml:space="preserve"><tspan
1137
       id="tspan2307"
1138
       sodipodi:role="line"
1139
       x="134.3277"
1140
       y="342.91147">XML</tspan></text>
1141
  <text
1142
     id="text2313"
1143
     y="370.34702"
1144
     x="113.56207"
1145
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1146
     xml:space="preserve"><tspan
1147
       id="tspan2311"
1148
       sodipodi:role="line"
1149
       x="113.56207"
1150
       y="370.34702">DocBook</tspan></text>
1151
  <text
1152
     id="text2317"
1153
     y="398.51498"
1154
     x="114.3526"
1155
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1156
     xml:space="preserve"><tspan
1157
       id="tspan2315"
1158
       sodipodi:role="line"
1159
       x="114.3526"
1160
       y="398.51498">AsciiDoc</tspan></text>
1161
  <text
1162
     transform="rotate(-90)"
1163
     id="text2329"
1164
     y="43.507812"
1165
     x="-774.87335"
1166
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1167
     xml:space="preserve"><tspan
1168
       y="43.507812"
1169
       x="-774.87335"
1170
       id="tspan2327"
1171
       sodipodi:role="line">Example Processing Combination</tspan></text>
1172
  <text
1173
     id="text2333"
1174
     y="562.05426"
1175
     x="135.31207"
1176
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#f3fbfe;fill-opacity:1;stroke:none"
1177
     xml:space="preserve"><tspan
1178
       y="562.05426"
1179
       x="135.31207"
1180
       id="tspan2331"
1181
       sodipodi:role="line">XML</tspan></text>
1182
  <text
1183
     id="text2337"
1184
     y="495.6918"
1185
     x="381.64142"
1186
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#333333;fill-opacity:0.866667;stroke:none"
1187
     xml:space="preserve"><tspan
1188
       y="495.6918"
1189
       x="381.64142"
1190
       id="tspan2335"
1191
       sodipodi:role="line">XSLT</tspan></text>
1192
  <text
1193
     id="text2341"
1194
     y="562.05426"
1195
     x="323.97742"
1196
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1197
     xml:space="preserve"><tspan
1198
       y="562.05426"
1199
       x="323.97742"
1200
       id="tspan2339"
1201
       sodipodi:role="line">XSLT Processor</tspan></text>
1202
  <text
1203
     id="text2345"
1204
     y="562.54059"
1205
     x="579.27557"
1206
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1207
     xml:space="preserve"><tspan
1208
       y="562.54059"
1209
       x="579.27557"
1210
       id="tspan2343"
1211
       sodipodi:role="line">R Markdown</tspan></text>
1212
  <text
1213
     id="text2349"
1214
     y="643.24084"
1215
     x="588.75018"
1216
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1217
     xml:space="preserve"><tspan
1218
       y="643.24084"
1219
       x="588.75018"
1220
       id="tspan2347"
1221
       sodipodi:role="line">Markdown</tspan></text>
1222
  <text
1223
     id="text2353"
1224
     y="642.63147"
1225
     x="339.61023"
1226
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1227
     xml:space="preserve"><tspan
1228
       y="642.63147"
1229
       x="339.61023"
1230
       id="tspan2351"
1231
       sodipodi:role="line">R Processor</tspan></text>
1232
  <text
1233
     id="text2357"
1234
     y="722.93903"
1235
     x="318.43912"
1236
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:21.3333px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1237
     xml:space="preserve"><tspan
1238
       y="722.93903"
1239
       x="318.43912"
1240
       id="tspan2355"
1241
       sodipodi:role="line">Variable Processor</tspan></text>
1242
  <text
1243
     id="text2361"
1244
     y="723.3316"
1245
     x="604.07831"
1246
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1247
     xml:space="preserve"><tspan
1248
       y="723.3316"
1249
       x="604.07831"
1250
       id="tspan2359"
1251
       sodipodi:role="line">HTML5</tspan></text>
1252
  <text
1253
     id="text2365"
1254
     y="630.84766"
1255
     x="81.211723"
1256
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#333333;fill-opacity:0.866667;stroke:none"
1257
     xml:space="preserve"><tspan
1258
       y="630.84766"
1259
       x="81.211723"
1260
       id="tspan2363"
1261
       sodipodi:role="line">Structured Data Source</tspan></text>
1262
  <text
1263
     id="text2369"
1264
     y="756.39404"
1265
     x="215.65826"
1266
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1267
     xml:space="preserve"><tspan
1268
       y="756.39404"
1269
       x="215.65826"
1270
       id="tspan2367"
1271
       sodipodi:role="line">interpolated values</tspan></text>
1272
  <g
1273
     transform="translate(-0.25585322,11.831789)"
1274
     id="g2523">
1275
    <text
1276
       xml:space="preserve"
1277
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1278
       x="156.49219"
1279
       y="708.2467"
1280
       id="text2373"><tspan
1281
         sodipodi:role="line"
1282
         id="tspan2371"
1283
         x="156.49219"
1284
         y="708.2467">CSON</tspan></text>
1285
    <text
1286
       xml:space="preserve"
1287
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1288
       x="156.49219"
1289
       y="688.41504"
1290
       id="text2377"><tspan
1291
         sodipodi:role="line"
1292
         id="tspan2375"
1293
         x="156.49219"
1294
         y="688.41504">JSONNET</tspan></text>
1295
    <text
1296
       xml:space="preserve"
1297
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1298
       x="156.49219"
1299
       y="668.24695"
1300
       id="text2381"><tspan
1301
         sodipodi:role="line"
1302
         id="tspan2379"
1303
         x="156.49219"
1304
         y="668.24695">JSON5</tspan></text>
1305
    <text
1306
       xml:space="preserve"
1307
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1308
       x="156.49219"
1309
       y="648.07886"
1310
       id="text2385"><tspan
1311
         sodipodi:role="line"
1312
         id="tspan2383"
1313
         x="156.49219"
1314
         y="648.07886">JSON</tspan></text>
1315
    <text
1316
       xml:space="preserve"
1317
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1318
       x="94.110725"
1319
       y="648.41534"
1320
       id="text2389"><tspan
1321
         sodipodi:role="line"
1322
         id="tspan2387"
1323
         x="94.110725"
1324
         y="648.41534">YAML</tspan></text>
1325
    <text
1326
       xml:space="preserve"
1327
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1328
       x="94.110725"
1329
       y="668.24695"
1330
       id="text2393"><tspan
1331
         sodipodi:role="line"
1332
         id="tspan2391"
1333
         x="94.110725"
1334
         y="668.24695">TOML</tspan></text>
1335
    <text
1336
       xml:space="preserve"
1337
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1338
       x="94.110725"
1339
       y="688.41504"
1340
       id="text2397"><tspan
1341
         sodipodi:role="line"
1342
         id="tspan2395"
1343
         x="94.110725"
1344
         y="688.41504">XML</tspan></text>
1345
  </g>
1346
  <g
1347
     transform="translate(-1.2304677,-0.85937628)"
1348
     id="g2593">
1349
    <g
1350
       id="g2532">
1351
      <rect
1352
         id="rect4698"
1353
         ry="2.7292624"
1354
         y="91.740654"
1355
         x="129.16347"
1356
         height="32.205296"
1357
         width="54.039394"
1358
         style="fill:none;stroke:#05556e;stroke-width:2.72926;stroke-opacity:1" />
1359
      <path
1360
         style="fill:#05556e;fill-opacity:1;stroke-width:0.272926"
1361
         id="path4700"
1362
         d="M 135.98663,117.12279 V 98.56381 h 5.45852 l 5.45853,6.82315 5.45852,-6.82315 h 5.45853 v 18.55898 h -5.45853 v -10.64412 l -5.45852,6.82315 -5.45853,-6.82315 v 10.64412 z m 34.11578,0 -8.18779,-9.00657 h 5.45852 v -9.55241 h 5.45853 v 9.55241 h 5.45852 z"
1363
         inkscape:connector-curvature="0" />
1364
    </g>
1365
    <text
1366
       xml:space="preserve"
1367
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1368
       x="108.73981"
1369
       y="152.80437"
1370
       id="text2407"><tspan
1371
         sodipodi:role="line"
1372
         id="tspan2405"
1373
         x="108.73981"
1374
         y="152.80437">Markdown</tspan></text>
1375
  </g>
1376
  <path
1377
     inkscape:connector-curvature="0"
1378
     d="m 417.86562,272.90923 c -2.81873,0.35302 -5.58858,1.78683 -7.90222,4.10047 -1.79226,1.78682 -3.43787,4.20365 -5.01832,7.35911 -1.28173,2.56347 -2.29191,5.21927 -2.90019,7.59265 l -0.1738,0.68975 -0.68975,0.35302 c -0.96673,0.49423 -1.81398,1.01561 -2.77528,1.69993 -3.29666,2.35709 -6.15341,5.19211 -8.53222,8.46705 -0.23354,0.32586 -0.45621,0.58656 -0.49966,0.58656 -0.038,0 -0.33673,-0.0435 -0.65716,-0.0923 -0.73863,-0.11949 -3.19891,-0.13578 -4.11676,-0.0272 -3.79633,0.46164 -7.25593,1.57502 -11.41613,3.68228 -3.00339,1.5207 -4.93685,2.87304 -6.8323,4.77391 -2.37881,2.37882 -3.80176,5.01832 -4.21452,7.82076 -0.0978,0.62457 -0.0978,2.39511 0,3.0414 0.51052,3.55193 2.55804,6.94636 5.27358,8.74404 3.15003,2.08554 7.40256,2.6558 12.27424,1.65105 3.62253,-0.75492 7.20161,-2.14527 10.77526,-4.19822 3.47046,-1.99321 5.87643,-4.18193 7.57093,-6.87575 0.27155,-0.43449 0.35845,-0.52682 0.53224,-0.59199 2.79701,-1.01018 4.74677,-2.05295 6.96265,-3.72572 2.02036,-1.5207 3.43244,-2.85675 6.0991,-5.77324 0.68432,-0.74949 0.8038,-0.91785 0.84182,-1.16225 0.0326,-0.17379 0.0543,-0.20095 0.15207,-0.17922 0.51595,0.10319 2.20502,0.11948 2.94908,0.0272 2.08553,-0.25526 4.05701,-1.10251 6.01763,-2.57976 2.61778,-1.97691 5.06177,-5.27901 6.78885,-9.17853 2.59606,-5.86556 3.57908,-10.80785 3.01425,-15.19073 -0.14121,-1.12423 -0.28241,-1.74881 -0.59742,-2.71554 -0.42905,-1.29803 -1.08621,-2.55804 -1.89001,-3.62796 -0.43449,-0.57026 -1.57502,-1.70536 -2.14528,-2.12898 -1.59131,-1.17855 -3.93753,-2.13442 -6.03936,-2.46028 -0.66259,-0.10319 -2.29735,-0.14664 -2.85132,-0.0815 z m 2.44399,7.82076 c 1.94433,0.46707 3.2152,2.04751 3.5302,4.39917 0.0815,0.58656 0.0815,2.10183 0,2.7427 -0.32043,2.62864 -1.26544,5.70263 -2.61235,8.48878 -1.01561,2.10725 -1.79226,3.34011 -2.88933,4.58383 -0.32587,0.36931 -1.38493,1.31975 -1.42838,1.2763 -0.005,-0.005 0.0706,-0.34216 0.1738,-0.74406 0.24983,-0.97759 0.34215,-1.56958 0.3856,-2.41683 0.0706,-1.58044 -0.27155,-3.09571 -0.98302,-4.30684 -1.20027,-2.05295 -3.17175,-3.41072 -5.47453,-3.78547 -0.11405,-0.0163 -0.20638,-0.0489 -0.20638,-0.076 0,-0.0217 0.19552,-0.53768 0.42905,-1.15139 1.41752,-3.67684 2.66666,-5.83298 4.30142,-7.40799 1.0482,-1.01562 1.70536,-1.40665 2.73726,-1.62933 0.51596,-0.11405 1.49355,-0.0978 2.03666,0.0272 z m -10.34078,17.93885 c 0.52139,0.54311 0.56483,0.76579 0.46164,2.25933 l -0.0326,0.51596 -0.14121,-0.21725 c -0.22811,-0.34215 -0.40733,-0.72233 -0.52682,-1.1188 -0.0652,-0.20095 -0.15207,-0.43992 -0.20095,-0.53224 -0.0706,-0.13035 -0.17922,-0.91243 -0.19008,-1.34691 0,-0.11949 0.29871,0.0923 0.63,0.43991 z m -7.36997,3.01425 c 0.3856,2.28649 1.18397,4.05159 2.44941,5.40393 l 0.45078,0.47793 -0.13577,0.14664 c -0.0706,0.0815 -0.46165,0.51052 -0.86355,0.9613 -1.55328,1.73795 -2.81873,2.98167 -4.05158,3.97012 -0.41819,0.34216 -0.78208,0.61915 -0.79837,0.61915 -0.0163,0 -0.0435,-0.0923 -0.0652,-0.20638 -0.076,-0.4019 -0.46708,-1.4664 -0.8038,-2.15614 -0.54311,-1.12424 -1.14596,-2.0095 -2.08554,-3.0577 l -0.45621,-0.50509 0.41276,-0.50509 c 1.19484,-1.47182 2.92192,-3.26951 4.43177,-4.62728 0.85811,-0.76578 1.37949,-1.21656 1.39578,-1.20027 0.005,0.005 0.0597,0.315 0.11949,0.67888 z m -16.52135,9.77052 c -0.0163,0.11405 -0.0815,0.54311 -0.14664,0.9613 -0.22267,1.47182 -0.23353,3.57365 -0.0272,4.78478 0.19008,1.10251 0.57569,2.11812 1.08078,2.81873 0.27699,0.38018 0.87441,0.97759 1.22199,1.20027 l 0.23354,0.1575 -0.15207,0.12492 c -0.60285,0.48879 -2.54174,1.58044 -4.18193,2.34622 -2.4114,1.12967 -4.36659,1.7651 -6.62049,2.16157 -0.77664,0.13578 -0.99932,0.15207 -2.09096,0.15207 -0.98846,0 -1.30889,-0.0217 -1.67278,-0.0978 -1.5207,-0.33672 -2.53088,-0.97216 -3.1989,-2.0095 -0.53225,-0.82552 -0.72234,-1.48268 -0.72777,-2.43855 0,-1.56415 0.57027,-2.68296 2.17244,-4.27969 1.78682,-1.77597 3.93753,-3.05227 7.72299,-4.5784 2.01493,-0.81467 4.20366,-1.37407 5.75151,-1.4664 0.74406,-0.0434 0.66803,-0.0652 0.63544,0.16294 z m 6.13712,3.5302 c -0.0163,0.0543 -0.0272,0.0109 -0.0272,-0.0923 0,-0.10319 0.0109,-0.14664 0.0272,-0.0978 0.0109,0.0543 0.0109,0.14121 0,0.19009 z"
1379
     id="path8164"
1380
     style="fill:#df4d65;fill-opacity:1;stroke:none;stroke-width:0.00543108" />
1381
  <g
1382
     transform="translate(1.378418e-5,1.0193503)"
1383
     id="g1168">
1384
    <text
1385
       id="text1158"
1386
       y="364.17905"
1387
       x="349.05551"
1388
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1389
       xml:space="preserve"><tspan
1390
         id="tspan1156"
1391
         sodipodi:role="line"
1392
         x="349.05551"
1393
         y="364.17905">Processor</tspan></text>
1394
    <text
1395
       xml:space="preserve"
1396
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1397
       x="370.40707"
1398
       y="392.17905"
1399
       id="text1162"><tspan
1400
         y="392.17905"
1401
         x="370.40707"
1402
         sodipodi:role="line"
1403
         id="tspan1160">Chain</tspan></text>
1404
  </g>
1405
  <g
1406
     transform="translate(0,-2.3144459)"
1407
     id="g1206">
1408
    <text
1409
       xml:space="preserve"
1410
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1411
       x="586.44855"
1412
       y="327.56967"
1413
       id="text1190"><tspan
1414
         y="327.56967"
1415
         x="586.44855"
1416
         sodipodi:role="line"
1417
         id="tspan1188">Processor-</tspan></text>
1418
    <text
1419
       id="text1194"
1420
       y="355.56967"
1421
       x="588.43488"
1422
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1423
       xml:space="preserve"><tspan
1424
         id="tspan1192"
1425
         sodipodi:role="line"
1426
         x="588.43488"
1427
         y="355.56967">dependent</tspan></text>
1428
  </g>
1429
</svg>
11430
A images/architecture/logos/html5.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
	<title>HTML5 Logo</title>
3
	<path d="M108.4 0h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206 23h-20.3V0h63.7v23H229v46h-23M259.5 0h24.1l14.8 24.3L313.2 0h24.1v69h-23V34.8l-16.1 24.8l-16.1-24.8v34.2h-22.6M348.7 0h23v46.2h32.6V69h-55.6"/>
4
	<path fill="#e44d26" d="M107.6 471l-33-370.4h362.8l-33 370.2L255.7 512"/>
5
	<path fill="#f16529" d="M256 480.5V131H404.3L376 447"/>
6
	<path fill="#ebebeb" d="M142 176.3h114v45.4h-64.2l4.2 46.5h60v45.3H154.4M156.4 336.3H202l3.2 36.3 50.8 13.6v47.4l-93.2-26"/>
7
	<path fill="#fff" d="M369.6 176.3H255.8v45.4h109.6M361.3 268.2H255.8v45.4h56l-5.3 59-50.7 13.6v47.2l93-25.8"/>
8
</svg>
A images/architecture/logos/links.svg
1
<?xml version="1.0" standalone="no"?>
2
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
3
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
4
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
5
 width="1280.000000pt" height="1123.000000pt" viewBox="0 0 1280.000000 1123.000000"
6
 preserveAspectRatio="xMidYMid meet">
7
<metadata>
8
Created by potrace 1.15, written by Peter Selinger 2001-2017
9
</metadata>
10
<g transform="translate(0.000000,1123.000000) scale(0.100000,-0.100000)"
11
fill="#000000" stroke="none">
12
<path d="M10280 11220 c-519 -65 -1029 -329 -1455 -755 -330 -329 -633 -774
13
-924 -1355 -236 -472 -422 -961 -534 -1398 l-32 -127 -127 -65 c-178 -91 -334
14
-187 -511 -313 -607 -434 -1133 -956 -1571 -1559 -43 -60 -84 -108 -92 -108
15
-7 0 -62 8 -121 17 -136 22 -589 25 -758 5 -699 -85 -1336 -290 -2102 -678
16
-553 -280 -909 -529 -1258 -879 -438 -438 -700 -924 -776 -1440 -18 -115 -18
17
-441 0 -560 94 -654 471 -1279 971 -1610 580 -384 1363 -489 2260 -304 667
18
139 1326 395 1984 773 639 367 1082 770 1394 1266 50 80 66 97 98 109 515 186
19
874 378 1282 686 372 280 632 526 1123 1063 126 138 148 169 155 214 6 32 10
20
37 28 33 95 -19 406 -22 543 -5 384 47 747 203 1108 475 482 364 932 972 1250
21
1690 478 1080 659 1990 555 2797 -26 207 -52 322 -110 500 -79 239 -200 471
22
-348 668 -80 105 -290 314 -395 392 -293 217 -725 393 -1112 453 -122 19 -423
23
27 -525 15z m450 -1440 c358 -86 592 -377 650 -810 15 -108 15 -387 0 -505
24
-59 -484 -233 -1050 -481 -1563 -187 -388 -330 -615 -532 -844 -60 -68 -255
25
-243 -263 -235 -1 1 13 63 32 137 46 180 63 289 71 445 13 291 -50 570 -181
26
793 -221 378 -584 628 -1008 697 -21 3 -38 9 -38 14 0 4 36 99 79 212 261 677
27
491 1074 792 1364 193 187 314 259 504 300 95 21 275 18 375 -5z m-1904 -3303
28
c96 -100 104 -141 85 -416 l-6 -95 -26 40 c-42 63 -75 133 -97 206 -12 37 -28
29
81 -37 98 -13 24 -33 168 -35 248 0 22 55 -17 116 -81z m-1357 -555 c71 -421
30
218 -746 451 -995 l83 -88 -25 -27 c-13 -15 -85 -94 -159 -177 -286 -320 -519
31
-549 -746 -731 -77 -63 -144 -114 -147 -114 -3 0 -8 17 -12 38 -14 74 -86 270
32
-148 397 -100 207 -211 370 -384 563 l-84 93 76 93 c220 271 538 602 816 852
33
158 141 254 224 257 221 1 -1 11 -58 22 -125z m-3042 -1799 c-3 -21 -15 -100
34
-27 -177 -41 -271 -43 -658 -5 -881 35 -203 106 -390 199 -519 51 -70 161
35
-180 225 -221 l43 -29 -28 -23 c-111 -90 -468 -291 -770 -432 -444 -208 -804
36
-325 -1219 -398 -143 -25 -184 -28 -385 -28 -182 0 -241 4 -308 18 -280 62
37
-466 179 -589 370 -98 152 -133 273 -134 449 0 288 105 494 400 788 329 327
38
725 562 1422 843 371 150 774 253 1059 270 137 8 123 12 117 -30z m1130 -650
39
c-3 -10 -5 -2 -5 17 0 19 2 27 5 18 2 -10 2 -26 0 -35z"/>
40
</g>
41
</svg>
142
A images/architecture/logos/markdown.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="208" height="128" viewBox="0 0 208 128"><rect width="198" height="118" x="5" y="5" ry="10" stroke="#000" stroke-width="10" fill="none"/><path d="M30 98V30h20l20 25 20-25h20v68H90V59L70 84 50 59v39zm125 0l-30-33h20V30h20v35h20z"/></svg>
A libs/jsymspell/jsymspell-core-1.0-SNAPSHOT-javadoc.jar
Binary file
A libs/jsymspell/jsymspell-core-1.0-SNAPSHOT-sources.jar
Binary file
A libs/jsymspell/jsymspell-core-1.0-SNAPSHOT.jar
Binary file
D src/main/java/com/scrivenvar/AbstractPane.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import org.tbee.javafx.scene.layout.fxml.MigPane;
31
32
/**
33
 * Hides dependency on {@link MigPane} from subclasses.
34
 */
35
public abstract class AbstractPane extends MigPane {
36
}
371
M src/main/java/com/scrivenvar/Constants.java
3030
import com.scrivenvar.service.Settings;
3131
32
import java.nio.file.Path;
33
import java.nio.file.Paths;
34
3235
/**
3336
 * Defines application-wide default values.
...
100103
  public static final int DEFAULT_MAP_SIZE = 64;
101104
105
  /**
106
   * Default image extension order to use when scanning.
107
   */
102108
  public static final String PERSIST_IMAGES_DEFAULT =
103
      get( "file.stylesheet.scene" );
109
      get( "file.ext.image.order" );
104110
105111
  /**
106112
   * Default working directory to use for R startup script.
107113
   */
108114
  public static final String USER_DIRECTORY = System.getProperty( "user.dir" );
115
116
  /**
117
   * Default path to use for an untitled (pathless) file.
118
   */
119
  public static final Path DEFAULT_DIRECTORY = Paths.get( USER_DIRECTORY );
120
121
  /**
122
   * Resource directory where different language lexicons are located.
123
   */
124
  public static final String LEXICONS_DIRECTORY = "lexicons";
109125
110126
  /**
M src/main/java/com/scrivenvar/Main.java
119119
              final var font = Font.createFont( Font.TRUETYPE_FONT, is );
120120
              final Map attributes = font.getAttributes();
121
121122
              attributes.put( LIGATURES, LIGATURES_ON );
122123
              ge.registerFont( font.deriveFont( attributes ) );
M src/main/java/com/scrivenvar/MainWindow.java
4444
import com.scrivenvar.service.Snitch;
4545
import com.scrivenvar.service.events.Notifier;
46
import com.scrivenvar.util.Action;
47
import com.scrivenvar.util.ActionBuilder;
48
import com.scrivenvar.util.ActionUtils;
49
import javafx.beans.binding.Bindings;
50
import javafx.beans.binding.BooleanBinding;
51
import javafx.beans.property.BooleanProperty;
52
import javafx.beans.property.SimpleBooleanProperty;
53
import javafx.beans.value.ChangeListener;
54
import javafx.beans.value.ObservableBooleanValue;
55
import javafx.beans.value.ObservableValue;
56
import javafx.collections.ListChangeListener.Change;
57
import javafx.collections.ObservableList;
58
import javafx.event.Event;
59
import javafx.event.EventHandler;
60
import javafx.geometry.Pos;
61
import javafx.scene.Node;
62
import javafx.scene.Scene;
63
import javafx.scene.control.*;
64
import javafx.scene.control.Alert.AlertType;
65
import javafx.scene.image.Image;
66
import javafx.scene.image.ImageView;
67
import javafx.scene.input.Clipboard;
68
import javafx.scene.input.ClipboardContent;
69
import javafx.scene.input.KeyEvent;
70
import javafx.scene.layout.BorderPane;
71
import javafx.scene.layout.VBox;
72
import javafx.scene.text.Text;
73
import javafx.stage.Window;
74
import javafx.stage.WindowEvent;
75
import javafx.util.Duration;
76
import org.apache.commons.lang3.SystemUtils;
77
import org.controlsfx.control.StatusBar;
78
import org.fxmisc.richtext.StyleClassedTextArea;
79
import org.reactfx.value.Val;
80
import org.xhtmlrenderer.util.XRLog;
81
82
import java.nio.file.Path;
83
import java.util.HashMap;
84
import java.util.Map;
85
import java.util.Observable;
86
import java.util.Observer;
87
import java.util.function.Function;
88
import java.util.prefs.Preferences;
89
90
import static com.scrivenvar.Constants.*;
91
import static com.scrivenvar.Messages.get;
92
import static com.scrivenvar.util.StageState.*;
93
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
94
import static javafx.application.Platform.runLater;
95
import static javafx.event.Event.fireEvent;
96
import static javafx.scene.input.KeyCode.ENTER;
97
import static javafx.scene.input.KeyCode.TAB;
98
import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
99
100
/**
101
 * Main window containing a tab pane in the center for file editors.
102
 */
103
public class MainWindow implements Observer {
104
  /**
105
   * The {@code OPTIONS} variable must be declared before all other variables
106
   * to prevent subsequent initializations from failing due to missing user
107
   * preferences.
108
   */
109
  private final static Options OPTIONS = Services.load( Options.class );
110
  private final static Snitch SNITCH = Services.load( Snitch.class );
111
  private final static Notifier NOTIFIER = Services.load( Notifier.class );
112
113
  private final Scene mScene;
114
  private final StatusBar mStatusBar;
115
  private final Text mLineNumberText;
116
  private final TextField mFindTextField;
117
118
  private final Object mMutex = new Object();
119
120
  /**
121
   * Prevents re-instantiation of processing classes.
122
   */
123
  private final Map<FileEditorTab, Processor<String>> mProcessors =
124
      new HashMap<>();
125
126
  private final Map<String, String> mResolvedMap =
127
      new HashMap<>( DEFAULT_MAP_SIZE );
128
129
  /**
130
   * Called when the definition data is changed.
131
   */
132
  private final EventHandler<TreeItem.TreeModificationEvent<Event>>
133
      mTreeHandler = event -> {
134
    exportDefinitions( getDefinitionPath() );
135
    interpolateResolvedMap();
136
    renderActiveTab();
137
  };
138
139
  /**
140
   * Called to switch to the definition pane when the user presses the TAB key.
141
   */
142
  private final EventHandler<? super KeyEvent> mTabKeyHandler =
143
      (EventHandler<KeyEvent>) event -> {
144
        if( event.getCode() == TAB ) {
145
          getDefinitionPane().requestFocus();
146
          event.consume();
147
        }
148
      };
149
150
  /**
151
   * Called to inject the selected item when the user presses ENTER in the
152
   * definition pane.
153
   */
154
  private final EventHandler<? super KeyEvent> mDefinitionKeyHandler =
155
      event -> {
156
        if( event.getCode() == ENTER ) {
157
          getVariableNameInjector().injectSelectedItem();
158
        }
159
      };
160
161
  private final ChangeListener<Integer> mCaretPositionListener =
162
      ( observable, oldPosition, newPosition ) -> {
163
        final FileEditorTab tab = getActiveFileEditorTab();
164
        final EditorPane pane = tab.getEditorPane();
165
        final StyleClassedTextArea editor = pane.getEditor();
166
167
        getLineNumberText().setText(
168
            get( STATUS_BAR_LINE,
169
                 editor.getCurrentParagraph() + 1,
170
                 editor.getParagraphs().size(),
171
                 editor.getCaretPosition()
172
            )
173
        );
174
      };
175
176
  private final ChangeListener<Integer> mCaretParagraphListener =
177
      ( observable, oldIndex, newIndex ) ->
178
          scrollToParagraph( newIndex, true );
179
180
  private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
181
  private final DefinitionPane mDefinitionPane = new DefinitionPane();
182
  private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
183
  private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
184
      mCaretPositionListener,
185
      mCaretParagraphListener );
186
187
  /**
188
   * Listens on the definition pane for double-click events.
189
   */
190
  private final VariableNameInjector mVariableNameInjector
191
      = new VariableNameInjector( mDefinitionPane );
192
193
  public MainWindow() {
194
    mStatusBar = createStatusBar();
195
    mLineNumberText = createLineNumberText();
196
    mFindTextField = createFindTextField();
197
    mScene = createScene();
198
199
    System.getProperties()
200
          .setProperty( "xr.util-logging.loggingEnabled", "true" );
201
    XRLog.setLoggingEnabled( true );
202
203
    initLayout();
204
    initFindInput();
205
    initSnitch();
206
    initDefinitionListener();
207
    initTabAddedListener();
208
    initTabChangedListener();
209
    initPreferences();
210
    initVariableNameInjector();
211
212
    NOTIFIER.addObserver( this );
213
  }
214
215
  private void initLayout() {
216
    final Scene appScene = getScene();
217
218
    appScene.getStylesheets().add( STYLESHEET_SCENE );
219
220
    // TODO: Apply an XML syntax highlighting for XML files.
221
//    appScene.getStylesheets().add( STYLESHEET_XML );
222
    appScene.windowProperty().addListener(
223
        ( observable, oldWindow, newWindow ) ->
224
            newWindow.setOnCloseRequest(
225
                e -> {
226
                  if( !getFileEditorPane().closeAllEditors() ) {
227
                    e.consume();
228
                  }
229
                }
230
            )
231
    );
232
  }
233
234
  /**
235
   * Initialize the find input text field to listen on F3, ENTER, and
236
   * ESCAPE key presses.
237
   */
238
  private void initFindInput() {
239
    final TextField input = getFindTextField();
240
241
    input.setOnKeyPressed( ( KeyEvent event ) -> {
242
      switch( event.getCode() ) {
243
        case F3:
244
        case ENTER:
245
          editFindNext();
246
          break;
247
        case F:
248
          if( !event.isControlDown() ) {
249
            break;
250
          }
251
        case ESCAPE:
252
          getStatusBar().setGraphic( null );
253
          getActiveFileEditorTab().getEditorPane().requestFocus();
254
          break;
255
      }
256
    } );
257
258
    // Remove when the input field loses focus.
259
    input.focusedProperty().addListener(
260
        ( focused, oldFocus, newFocus ) -> {
261
          if( !newFocus ) {
262
            getStatusBar().setGraphic( null );
263
          }
264
        }
265
    );
266
  }
267
268
  /**
269
   * Watch for changes to external files. In particular, this awaits
270
   * modifications to any XSL files associated with XML files being edited.
271
   * When
272
   * an XSL file is modified (external to the application), the snitch's ears
273
   * perk up and the file is reloaded. This keeps the XSL transformation up to
274
   * date with what's on the file system.
275
   */
276
  private void initSnitch() {
277
    SNITCH.addObserver( this );
278
  }
279
280
  /**
281
   * Listen for {@link FileEditorTabPane} to receive open definition file
282
   * event.
283
   */
284
  private void initDefinitionListener() {
285
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
286
        ( final ObservableValue<? extends Path> file,
287
          final Path oldPath, final Path newPath ) -> {
288
          // Indirectly refresh the resolved map.
289
          resetProcessors();
290
291
          openDefinitions( newPath );
292
293
          // Will create new processors and therefore a new resolved map.
294
          renderActiveTab();
295
        }
296
    );
297
  }
298
299
  /**
300
   * When tabs are added, hook the various change listeners onto the new
301
   * tab sothat the preview pane refreshes as necessary.
302
   */
303
  private void initTabAddedListener() {
304
    final FileEditorTabPane editorPane = getFileEditorPane();
305
306
    // Make sure the text processor kicks off when new files are opened.
307
    final ObservableList<Tab> tabs = editorPane.getTabs();
308
309
    // Update the preview pane on tab changes.
310
    tabs.addListener(
311
        ( final Change<? extends Tab> change ) -> {
312
          while( change.next() ) {
313
            if( change.wasAdded() ) {
314
              // Multiple tabs can be added simultaneously.
315
              for( final Tab newTab : change.getAddedSubList() ) {
316
                final FileEditorTab tab = (FileEditorTab) newTab;
317
318
                initTextChangeListener( tab );
319
                initTabKeyEventListener( tab );
320
                initScrollEventListener( tab );
321
//              initSyntaxListener( tab );
322
              }
323
            }
324
          }
325
        }
326
    );
327
  }
328
329
  private void initScrollEventListener( final FileEditorTab tab ) {
330
    final var scrollPane = tab.getScrollPane();
331
    final var scrollBar = getPreviewPane().getVerticalScrollBar();
332
333
    // Before the drag handler can be attached, the scroll bar for the
334
    // text editor pane must be visible.
335
    final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
336
        runLater( () -> {
337
          if( newShow ) {
338
            final var handler = new ScrollEventHandler( scrollPane, scrollBar );
339
            handler.enabledProperty().bind( tab.selectedProperty() );
340
          }
341
        } );
342
343
    Val.flatMap( scrollPane.sceneProperty(), Scene::windowProperty )
344
       .flatMap( Window::showingProperty )
345
       .addListener( listener );
346
  }
347
348
  /**
349
   * Listen for new tab selection events.
350
   */
351
  private void initTabChangedListener() {
352
    final FileEditorTabPane editorPane = getFileEditorPane();
353
354
    // Update the preview pane changing tabs.
355
    editorPane.addTabSelectionListener(
356
        ( tabPane, oldTab, newTab ) -> {
357
          if( newTab == null ) {
358
            getPreviewPane().clear();
359
          }
360
361
          // If there was no old tab, then this is a first time load, which
362
          // can be ignored.
363
          if( oldTab != null ) {
364
            if( newTab != null ) {
365
              final FileEditorTab tab = (FileEditorTab) newTab;
366
              updateVariableNameInjector( tab );
367
              process( tab );
368
            }
369
          }
370
        }
371
    );
372
  }
373
374
  /**
375
   * Reloads the preferences from the previous session.
376
   */
377
  private void initPreferences() {
378
    initDefinitionPane();
379
    getFileEditorPane().initPreferences();
380
  }
381
382
  private void initVariableNameInjector() {
383
    updateVariableNameInjector( getActiveFileEditorTab() );
384
  }
385
386
  /**
387
   * Ensure that the keyboard events are received when a new tab is added
388
   * to the user interface.
389
   *
390
   * @param tab The tab editor that can trigger keyboard events.
391
   */
392
  private void initTabKeyEventListener( final FileEditorTab tab ) {
393
    tab.addEventFilter( KeyEvent.KEY_PRESSED, mTabKeyHandler );
394
  }
395
396
  private void initTextChangeListener( final FileEditorTab tab ) {
397
    tab.addTextChangeListener(
398
        ( editor, oldValue, newValue ) -> {
399
          process( tab );
400
          scrollToParagraph( getCurrentParagraphIndex() );
401
        }
402
    );
403
  }
404
405
  private int getCurrentParagraphIndex() {
406
    return getActiveEditorPane().getCurrentParagraphIndex();
407
  }
408
409
  private void scrollToParagraph( final int id ) {
410
    scrollToParagraph( id, false );
411
  }
412
413
  /**
414
   * @param id    The paragraph to scroll to, will be approximated if it doesn't
415
   *              exist.
416
   * @param force {@code true} means to force scrolling immediately, which
417
   *              should only be attempted when it is known that the document
418
   *              has been fully rendered. Otherwise the internal map of ID
419
   *              attributes will be incomplete and scrolling will flounder.
420
   */
421
  private void scrollToParagraph( final int id, final boolean force ) {
422
    synchronized( mMutex ) {
423
      final var previewPane = getPreviewPane();
424
      final var scrollPane = previewPane.getScrollPane();
425
      final int approxId = getActiveEditorPane().approximateParagraphId( id );
426
427
      if( force ) {
428
        previewPane.scrollTo( approxId );
429
      }
430
      else {
431
        previewPane.tryScrollTo( approxId );
432
      }
433
434
      scrollPane.repaint();
435
    }
436
  }
437
438
  private void updateVariableNameInjector( final FileEditorTab tab ) {
439
    getVariableNameInjector().addListener( tab );
440
  }
441
442
  /**
443
   * Called whenever the preview pane becomes out of sync with the file editor
444
   * tab. This can be called when the text changes, the caret paragraph
445
   * changes, or the file tab changes.
446
   *
447
   * @param tab The file editor tab that has been changed in some fashion.
448
   */
449
  private void process( final FileEditorTab tab ) {
450
    if( tab == null ) {
451
      return;
452
    }
453
454
    getPreviewPane().setPath( tab.getPath() );
455
456
    final Processor<String> processor = getProcessors().computeIfAbsent(
457
        tab, p -> createProcessors( tab )
458
    );
459
460
    try {
461
      processChain( processor, tab.getEditorText() );
462
    } catch( final Exception ex ) {
463
      error( ex );
464
    }
465
  }
466
467
  /**
468
   * Executes the processing chain, operating on the given string.
469
   *
470
   * @param handler The first processor in the chain to call.
471
   * @param text    The initial value of the text to process.
472
   * @return The final value of the text that was processed by the chain.
473
   */
474
  private String processChain( Processor<String> handler, String text ) {
475
    while( handler != null && text != null ) {
476
      text = handler.process( text );
477
      handler = handler.next();
478
    }
479
480
    return text;
481
  }
482
483
  private void renderActiveTab() {
484
    process( getActiveFileEditorTab() );
485
  }
486
487
  /**
488
   * Called when a definition source is opened.
489
   *
490
   * @param path Path to the definition source that was opened.
491
   */
492
  private void openDefinitions( final Path path ) {
493
    try {
494
      final DefinitionSource ds = createDefinitionSource( path );
495
      setDefinitionSource( ds );
496
      getUserPreferences().definitionPathProperty().setValue( path.toFile() );
497
      getUserPreferences().save();
498
499
      final Tooltip tooltipPath = new Tooltip( path.toString() );
500
      tooltipPath.setShowDelay( Duration.millis( 200 ) );
501
502
      final DefinitionPane pane = getDefinitionPane();
503
      pane.update( ds );
504
      pane.addTreeChangeHandler( mTreeHandler );
505
      pane.addKeyEventHandler( mDefinitionKeyHandler );
506
      pane.filenameProperty().setValue( path.getFileName().toString() );
507
      pane.setTooltip( tooltipPath );
508
509
      interpolateResolvedMap();
510
    } catch( final Exception e ) {
511
      error( e );
512
    }
513
  }
514
515
  private void exportDefinitions( final Path path ) {
516
    try {
517
      final DefinitionPane pane = getDefinitionPane();
518
      final TreeItem<String> root = pane.getTreeView().getRoot();
519
      final TreeItem<String> problemChild = pane.isTreeWellFormed();
520
521
      if( problemChild == null ) {
522
        getDefinitionSource().getTreeAdapter().export( root, path );
523
        getNotifier().clear();
524
      }
525
      else {
526
        final String msg = get(
527
            "yaml.error.tree.form", problemChild.getValue() );
528
        getNotifier().notify( msg );
529
      }
530
    } catch( final Exception e ) {
531
      error( e );
532
    }
533
  }
534
535
  private void interpolateResolvedMap() {
536
    final Map<String, String> treeMap = getDefinitionPane().toMap();
537
    final Map<String, String> map = new HashMap<>( treeMap );
538
    MapInterpolator.interpolate( map );
539
540
    getResolvedMap().clear();
541
    getResolvedMap().putAll( map );
542
  }
543
544
  private void initDefinitionPane() {
545
    openDefinitions( getDefinitionPath() );
546
  }
547
548
  /**
549
   * Called when an exception occurs that warrants the user's attention.
550
   *
551
   * @param e The exception with a message that the user should know about.
552
   */
553
  private void error( final Exception e ) {
554
    getNotifier().notify( e );
555
  }
556
557
  //---- File actions -------------------------------------------------------
558
559
  /**
560
   * Called when an {@link Observable} instance has changed. This is called
561
   * by both the {@link Snitch} service and the notify service. The @link
562
   * Snitch} service can be called for different file types, including
563
   * {@link DefinitionSource} instances.
564
   *
565
   * @param observable The observed instance.
566
   * @param value      The noteworthy item.
567
   */
568
  @Override
569
  public void update( final Observable observable, final Object value ) {
570
    if( value != null ) {
571
      if( observable instanceof Snitch && value instanceof Path ) {
572
        updateSelectedTab();
573
      }
574
      else if( observable instanceof Notifier && value instanceof String ) {
575
        updateStatusBar( (String) value );
576
      }
577
    }
578
  }
579
580
  /**
581
   * Updates the status bar to show the given message.
582
   *
583
   * @param s The message to show in the status bar.
584
   */
585
  private void updateStatusBar( final String s ) {
586
    runLater(
587
        () -> {
588
          final int index = s.indexOf( '\n' );
589
          final String message = s.substring(
590
              0, index > 0 ? index : s.length() );
591
592
          getStatusBar().setText( message );
593
        }
594
    );
595
  }
596
597
  /**
598
   * Called when a file has been modified.
599
   */
600
  private void updateSelectedTab() {
601
    runLater(
602
        () -> {
603
          // Brute-force XSLT file reload by re-instantiating all processors.
604
          resetProcessors();
605
          renderActiveTab();
606
        }
607
    );
608
  }
609
610
  /**
611
   * After resetting the processors, they will refresh anew to be up-to-date
612
   * with the files (text and definition) currently loaded into the editor.
613
   */
614
  private void resetProcessors() {
615
    getProcessors().clear();
616
  }
617
618
  //---- File actions -------------------------------------------------------
619
620
  private void fileNew() {
621
    getFileEditorPane().newEditor();
622
  }
623
624
  private void fileOpen() {
625
    getFileEditorPane().openFileDialog();
626
  }
627
628
  private void fileClose() {
629
    getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
630
  }
631
632
  /**
633
   * TODO: Upon closing, first remove the tab change listeners. (There's no
634
   * need to re-render each tab when all are being closed.)
635
   */
636
  private void fileCloseAll() {
637
    getFileEditorPane().closeAllEditors();
638
  }
639
640
  private void fileSave() {
641
    getFileEditorPane().saveEditor( getActiveFileEditorTab() );
642
  }
643
644
  private void fileSaveAs() {
645
    final FileEditorTab editor = getActiveFileEditorTab();
646
    getFileEditorPane().saveEditorAs( editor );
647
    getProcessors().remove( editor );
648
649
    try {
650
      process( editor );
651
    } catch( final Exception ex ) {
652
      getNotifier().notify( ex );
653
    }
654
  }
655
656
  private void fileSaveAll() {
657
    getFileEditorPane().saveAllEditors();
658
  }
659
660
  private void fileExit() {
661
    final Window window = getWindow();
662
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
663
  }
664
665
  //---- Edit actions -------------------------------------------------------
666
667
  /**
668
   * Transform the Markdown into HTML then copy that HTML into the copy
669
   * buffer.
670
   */
671
  private void copyHtml() {
672
    final var markdown = getActiveEditorPane().getText();
673
    final var processors = createProcessorFactory().createProcessors(
674
        getActiveFileEditorTab()
675
    );
676
677
    final var chain = processors.remove( HtmlPreviewProcessor.class );
678
679
    final String html = processChain( chain, markdown );
680
681
    final Clipboard clipboard = Clipboard.getSystemClipboard();
682
    final ClipboardContent content = new ClipboardContent();
683
    content.putString( html );
684
    clipboard.setContent( content );
685
  }
686
687
  /**
688
   * Used to find text in the active file editor window.
689
   */
690
  private void editFind() {
691
    final TextField input = getFindTextField();
692
    getStatusBar().setGraphic( input );
693
    input.requestFocus();
694
  }
695
696
  public void editFindNext() {
697
    getActiveFileEditorTab().searchNext( getFindTextField().getText() );
698
  }
699
700
  public void editPreferences() {
701
    getUserPreferences().show();
702
  }
703
704
  //---- Insert actions -----------------------------------------------------
705
706
  /**
707
   * Delegates to the active editor to handle wrapping the current text
708
   * selection with leading and trailing strings.
709
   *
710
   * @param leading  The string to put before the selection.
711
   * @param trailing The string to put after the selection.
712
   */
713
  private void insertMarkdown(
714
      final String leading, final String trailing ) {
715
    getActiveEditorPane().surroundSelection( leading, trailing );
716
  }
717
718
  private void insertMarkdown(
719
      final String leading, final String trailing, final String hint ) {
720
    getActiveEditorPane().surroundSelection( leading, trailing, hint );
721
  }
722
723
  //---- Help actions -------------------------------------------------------
724
725
  private void helpAbout() {
726
    final Alert alert = new Alert( AlertType.INFORMATION );
727
    alert.setTitle( get( "Dialog.about.title" ) );
728
    alert.setHeaderText( get( "Dialog.about.header" ) );
729
    alert.setContentText( get( "Dialog.about.content" ) );
730
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
731
    alert.initOwner( getWindow() );
732
733
    alert.showAndWait();
734
  }
735
736
  //---- Member creators ----------------------------------------------------
737
738
  /**
739
   * Factory to create processors that are suited to different file types.
740
   *
741
   * @param tab The tab that is subjected to processing.
742
   * @return A processor suited to the file type specified by the tab's path.
743
   */
744
  private Processor<String> createProcessors( final FileEditorTab tab ) {
745
    return createProcessorFactory().createProcessors( tab );
746
  }
747
748
  private ProcessorFactory createProcessorFactory() {
749
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
750
  }
751
752
  private HTMLPreviewPane createHTMLPreviewPane() {
753
    return new HTMLPreviewPane();
754
  }
755
756
  private DefinitionSource createDefaultDefinitionSource() {
757
    return new YamlDefinitionSource( getDefinitionPath() );
758
  }
759
760
  private DefinitionSource createDefinitionSource( final Path path ) {
761
    try {
762
      return createDefinitionFactory().createDefinitionSource( path );
763
    } catch( final Exception ex ) {
764
      error( ex );
765
      return createDefaultDefinitionSource();
766
    }
767
  }
768
769
  private TextField createFindTextField() {
770
    return new TextField();
771
  }
772
773
  private DefinitionFactory createDefinitionFactory() {
774
    return new DefinitionFactory();
775
  }
776
777
  private StatusBar createStatusBar() {
778
    return new StatusBar();
779
  }
780
781
  private Scene createScene() {
782
    final SplitPane splitPane = new SplitPane(
783
        getDefinitionPane().getNode(),
784
        getFileEditorPane().getNode(),
785
        getPreviewPane().getNode() );
786
787
    splitPane.setDividerPositions(
788
        getFloat( K_PANE_SPLIT_DEFINITION, .10f ),
789
        getFloat( K_PANE_SPLIT_EDITOR, .45f ),
790
        getFloat( K_PANE_SPLIT_PREVIEW, .45f ) );
791
792
    getDefinitionPane().prefHeightProperty()
793
                       .bind( splitPane.heightProperty() );
794
795
    final BorderPane borderPane = new BorderPane();
796
    borderPane.setPrefSize( 1024, 800 );
797
    borderPane.setTop( createMenuBar() );
798
    borderPane.setBottom( getStatusBar() );
799
    borderPane.setCenter( splitPane );
800
801
    final VBox statusBar = new VBox();
802
    statusBar.setAlignment( Pos.BASELINE_CENTER );
803
    statusBar.getChildren().add( getLineNumberText() );
804
    getStatusBar().getRightItems().add( statusBar );
805
806
    // Force preview pane refresh on Windows.
807
    splitPane.getDividers().get( 1 ).positionProperty().addListener(
808
        ( l, oValue, nValue ) -> runLater(
809
            () -> {
810
              if( SystemUtils.IS_OS_WINDOWS ) {
811
                getPreviewPane().getScrollPane().repaint();
812
              }
813
            }
814
        )
815
    );
816
817
    return new Scene( borderPane );
818
  }
819
820
  private Text createLineNumberText() {
821
    return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) );
822
  }
823
824
  private Node createMenuBar() {
825
    final BooleanBinding activeFileEditorIsNull =
826
        getFileEditorPane().activeFileEditorProperty().isNull();
827
828
    // File actions
829
    final Action fileNewAction = new ActionBuilder()
830
        .setText( "Main.menu.file.new" )
831
        .setAccelerator( "Shortcut+N" )
832
        .setIcon( FILE_ALT )
833
        .setAction( e -> fileNew() )
834
        .build();
835
    final Action fileOpenAction = new ActionBuilder()
836
        .setText( "Main.menu.file.open" )
837
        .setAccelerator( "Shortcut+O" )
838
        .setIcon( FOLDER_OPEN_ALT )
839
        .setAction( e -> fileOpen() )
840
        .build();
841
    final Action fileCloseAction = new ActionBuilder()
842
        .setText( "Main.menu.file.close" )
843
        .setAccelerator( "Shortcut+W" )
844
        .setAction( e -> fileClose() )
845
        .setDisable( activeFileEditorIsNull )
846
        .build();
847
    final Action fileCloseAllAction = new ActionBuilder()
848
        .setText( "Main.menu.file.close_all" )
849
        .setAction( e -> fileCloseAll() )
850
        .setDisable( activeFileEditorIsNull )
851
        .build();
852
    final Action fileSaveAction = new ActionBuilder()
853
        .setText( "Main.menu.file.save" )
854
        .setAccelerator( "Shortcut+S" )
855
        .setIcon( FLOPPY_ALT )
856
        .setAction( e -> fileSave() )
857
        .setDisable( createActiveBooleanProperty(
858
            FileEditorTab::modifiedProperty ).not() )
859
        .build();
860
    final Action fileSaveAsAction = new ActionBuilder()
861
        .setText( "Main.menu.file.save_as" )
862
        .setAction( e -> fileSaveAs() )
863
        .setDisable( activeFileEditorIsNull )
864
        .build();
865
    final Action fileSaveAllAction = new ActionBuilder()
866
        .setText( "Main.menu.file.save_all" )
867
        .setAccelerator( "Shortcut+Shift+S" )
868
        .setAction( e -> fileSaveAll() )
869
        .setDisable( Bindings.not(
870
            getFileEditorPane().anyFileEditorModifiedProperty() ) )
871
        .build();
872
    final Action fileExitAction = new ActionBuilder()
873
        .setText( "Main.menu.file.exit" )
874
        .setAction( e -> fileExit() )
875
        .build();
876
877
    // Edit actions
878
    final Action editCopyHtmlAction = new ActionBuilder()
879
        .setText( Messages.get( "Main.menu.edit.copy.html" ) )
880
        .setIcon( HTML5 )
881
        .setAction( e -> copyHtml() )
882
        .setDisable( activeFileEditorIsNull )
883
        .build();
884
885
    final Action editUndoAction = new ActionBuilder()
886
        .setText( "Main.menu.edit.undo" )
887
        .setAccelerator( "Shortcut+Z" )
888
        .setIcon( UNDO )
889
        .setAction( e -> getActiveEditorPane().undo() )
890
        .setDisable( createActiveBooleanProperty(
891
            FileEditorTab::canUndoProperty ).not() )
892
        .build();
893
    final Action editRedoAction = new ActionBuilder()
894
        .setText( "Main.menu.edit.redo" )
895
        .setAccelerator( "Shortcut+Y" )
896
        .setIcon( REPEAT )
897
        .setAction( e -> getActiveEditorPane().redo() )
898
        .setDisable( createActiveBooleanProperty(
899
            FileEditorTab::canRedoProperty ).not() )
900
        .build();
901
902
    final Action editCutAction = new ActionBuilder()
903
        .setText( Messages.get( "Main.menu.edit.cut" ) )
904
        .setAccelerator( "Shortcut+X" )
905
        .setIcon( CUT )
906
        .setAction( e -> getActiveEditorPane().cut() )
907
        .setDisable( activeFileEditorIsNull )
908
        .build();
909
    final Action editCopyAction = new ActionBuilder()
910
        .setText( Messages.get( "Main.menu.edit.copy" ) )
911
        .setAccelerator( "Shortcut+C" )
912
        .setIcon( COPY )
913
        .setAction( e -> getActiveEditorPane().copy() )
914
        .setDisable( activeFileEditorIsNull )
915
        .build();
916
    final Action editPasteAction = new ActionBuilder()
917
        .setText( Messages.get( "Main.menu.edit.paste" ) )
918
        .setAccelerator( "Shortcut+V" )
919
        .setIcon( PASTE )
920
        .setAction( e -> getActiveEditorPane().paste() )
921
        .setDisable( activeFileEditorIsNull )
922
        .build();
923
    final Action editSelectAllAction = new ActionBuilder()
924
        .setText( Messages.get( "Main.menu.edit.selectAll" ) )
925
        .setAccelerator( "Shortcut+A" )
926
        .setAction( e -> getActiveEditorPane().selectAll() )
927
        .setDisable( activeFileEditorIsNull )
928
        .build();
929
930
    final Action editFindAction = new ActionBuilder()
931
        .setText( "Main.menu.edit.find" )
932
        .setAccelerator( "Ctrl+F" )
933
        .setIcon( SEARCH )
934
        .setAction( e -> editFind() )
935
        .setDisable( activeFileEditorIsNull )
936
        .build();
937
    final Action editFindNextAction = new ActionBuilder()
938
        .setText( "Main.menu.edit.find.next" )
939
        .setAccelerator( "F3" )
940
        .setIcon( null )
941
        .setAction( e -> editFindNext() )
942
        .setDisable( activeFileEditorIsNull )
943
        .build();
944
    final Action editPreferencesAction = new ActionBuilder()
945
        .setText( "Main.menu.edit.preferences" )
946
        .setAccelerator( "Ctrl+Alt+S" )
947
        .setAction( e -> editPreferences() )
948
        .build();
949
950
    // Insert actions
951
    final Action insertBoldAction = new ActionBuilder()
952
        .setText( "Main.menu.insert.bold" )
953
        .setAccelerator( "Shortcut+B" )
954
        .setIcon( BOLD )
955
        .setAction( e -> insertMarkdown( "**", "**" ) )
956
        .setDisable( activeFileEditorIsNull )
957
        .build();
958
    final Action insertItalicAction = new ActionBuilder()
959
        .setText( "Main.menu.insert.italic" )
960
        .setAccelerator( "Shortcut+I" )
961
        .setIcon( ITALIC )
962
        .setAction( e -> insertMarkdown( "*", "*" ) )
963
        .setDisable( activeFileEditorIsNull )
964
        .build();
965
    final Action insertSuperscriptAction = new ActionBuilder()
966
        .setText( "Main.menu.insert.superscript" )
967
        .setAccelerator( "Shortcut+[" )
968
        .setIcon( SUPERSCRIPT )
969
        .setAction( e -> insertMarkdown( "^", "^" ) )
970
        .setDisable( activeFileEditorIsNull )
971
        .build();
972
    final Action insertSubscriptAction = new ActionBuilder()
973
        .setText( "Main.menu.insert.subscript" )
974
        .setAccelerator( "Shortcut+]" )
975
        .setIcon( SUBSCRIPT )
976
        .setAction( e -> insertMarkdown( "~", "~" ) )
977
        .setDisable( activeFileEditorIsNull )
978
        .build();
979
    final Action insertStrikethroughAction = new ActionBuilder()
980
        .setText( "Main.menu.insert.strikethrough" )
981
        .setAccelerator( "Shortcut+T" )
982
        .setIcon( STRIKETHROUGH )
983
        .setAction( e -> insertMarkdown( "~~", "~~" ) )
984
        .setDisable( activeFileEditorIsNull )
985
        .build();
986
    final Action insertBlockquoteAction = new ActionBuilder()
987
        .setText( "Main.menu.insert.blockquote" )
988
        .setAccelerator( "Ctrl+Q" )
989
        .setIcon( QUOTE_LEFT )
990
        .setAction( e -> insertMarkdown( "\n\n> ", "" ) )
991
        .setDisable( activeFileEditorIsNull )
992
        .build();
993
    final Action insertCodeAction = new ActionBuilder()
994
        .setText( "Main.menu.insert.code" )
995
        .setAccelerator( "Shortcut+K" )
996
        .setIcon( CODE )
997
        .setAction( e -> insertMarkdown( "`", "`" ) )
998
        .setDisable( activeFileEditorIsNull )
999
        .build();
1000
    final Action insertFencedCodeBlockAction = new ActionBuilder()
1001
        .setText( "Main.menu.insert.fenced_code_block" )
1002
        .setAccelerator( "Shortcut+Shift+K" )
1003
        .setIcon( FILE_CODE_ALT )
1004
        .setAction( e -> insertMarkdown(
1005
            "\n\n```\n",
1006
            "\n```\n\n",
1007
            get( "Main.menu.insert.fenced_code_block.prompt" ) ) )
1008
        .setDisable( activeFileEditorIsNull )
1009
        .build();
1010
    final Action insertLinkAction = new ActionBuilder()
1011
        .setText( "Main.menu.insert.link" )
1012
        .setAccelerator( "Shortcut+L" )
1013
        .setIcon( LINK )
1014
        .setAction( e -> getActiveEditorPane().insertLink() )
1015
        .setDisable( activeFileEditorIsNull )
1016
        .build();
1017
    final Action insertImageAction = new ActionBuilder()
1018
        .setText( "Main.menu.insert.image" )
1019
        .setAccelerator( "Shortcut+G" )
1020
        .setIcon( PICTURE_ALT )
1021
        .setAction( e -> getActiveEditorPane().insertImage() )
1022
        .setDisable( activeFileEditorIsNull )
1023
        .build();
1024
1025
    // Number of header actions (H1 ... H3)
1026
    final int HEADERS = 3;
1027
    final Action[] headers = new Action[ HEADERS ];
1028
1029
    for( int i = 1; i <= HEADERS; i++ ) {
1030
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
1031
      final String markup = String.format( "%n%n%s ", hashes );
1032
      final String text = "Main.menu.insert.header." + i;
1033
      final String accelerator = "Shortcut+" + i;
1034
      final String prompt = text + ".prompt";
1035
1036
      headers[ i - 1 ] = new ActionBuilder()
1037
          .setText( text )
1038
          .setAccelerator( accelerator )
1039
          .setIcon( HEADER )
1040
          .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) )
1041
          .setDisable( activeFileEditorIsNull )
1042
          .build();
1043
    }
1044
1045
    final Action insertUnorderedListAction = new ActionBuilder()
1046
        .setText( "Main.menu.insert.unordered_list" )
1047
        .setAccelerator( "Shortcut+U" )
1048
        .setIcon( LIST_UL )
1049
        .setAction( e -> insertMarkdown( "\n\n* ", "" ) )
1050
        .setDisable( activeFileEditorIsNull )
1051
        .build();
1052
    final Action insertOrderedListAction = new ActionBuilder()
1053
        .setText( "Main.menu.insert.ordered_list" )
1054
        .setAccelerator( "Shortcut+Shift+O" )
1055
        .setIcon( LIST_OL )
1056
        .setAction( e -> insertMarkdown(
1057
            "\n\n1. ", "" ) )
1058
        .setDisable( activeFileEditorIsNull )
1059
        .build();
1060
    final Action insertHorizontalRuleAction = new ActionBuilder()
1061
        .setText( "Main.menu.insert.horizontal_rule" )
1062
        .setAccelerator( "Shortcut+H" )
1063
        .setAction( e -> insertMarkdown(
1064
            "\n\n---\n\n", "" ) )
1065
        .setDisable( activeFileEditorIsNull )
1066
        .build();
1067
1068
    // Help actions
1069
    final Action helpAboutAction = new ActionBuilder()
1070
        .setText( "Main.menu.help.about" )
1071
        .setAction( e -> helpAbout() )
1072
        .build();
1073
1074
    //---- MenuBar ----
1075
    final Menu fileMenu = ActionUtils.createMenu(
1076
        get( "Main.menu.file" ),
1077
        fileNewAction,
1078
        fileOpenAction,
1079
        null,
1080
        fileCloseAction,
1081
        fileCloseAllAction,
1082
        null,
1083
        fileSaveAction,
1084
        fileSaveAsAction,
1085
        fileSaveAllAction,
1086
        null,
1087
        fileExitAction );
1088
1089
    final Menu editMenu = ActionUtils.createMenu(
1090
        get( "Main.menu.edit" ),
1091
        editCopyHtmlAction,
1092
        null,
1093
        editUndoAction,
1094
        editRedoAction,
1095
        null,
1096
        editCutAction,
1097
        editCopyAction,
1098
        editPasteAction,
1099
        null,
1100
        editFindAction,
1101
        editFindNextAction,
1102
        null,
1103
        editPreferencesAction );
1104
1105
    final Menu insertMenu = ActionUtils.createMenu(
1106
        get( "Main.menu.insert" ),
1107
        insertBoldAction,
1108
        insertItalicAction,
1109
        insertSuperscriptAction,
1110
        insertSubscriptAction,
1111
        insertStrikethroughAction,
1112
        insertBlockquoteAction,
1113
        insertCodeAction,
1114
        insertFencedCodeBlockAction,
1115
        null,
1116
        insertLinkAction,
1117
        insertImageAction,
1118
        null,
1119
        headers[ 0 ],
1120
        headers[ 1 ],
1121
        headers[ 2 ],
1122
        null,
1123
        insertUnorderedListAction,
1124
        insertOrderedListAction,
1125
        insertHorizontalRuleAction
1126
    );
1127
1128
    final Menu helpMenu = ActionUtils.createMenu(
1129
        get( "Main.menu.help" ),
1130
        helpAboutAction );
1131
1132
    final MenuBar menuBar = new MenuBar(
1133
        fileMenu,
1134
        editMenu,
1135
        insertMenu,
1136
        helpMenu );
1137
1138
    //---- ToolBar ----
1139
    final ToolBar toolBar = ActionUtils.createToolBar(
1140
        fileNewAction,
1141
        fileOpenAction,
1142
        fileSaveAction,
1143
        null,
1144
        editUndoAction,
1145
        editRedoAction,
1146
        editCutAction,
1147
        editCopyAction,
1148
        editPasteAction,
1149
        null,
1150
        insertBoldAction,
1151
        insertItalicAction,
1152
        insertSuperscriptAction,
1153
        insertSubscriptAction,
1154
        insertBlockquoteAction,
1155
        insertCodeAction,
1156
        insertFencedCodeBlockAction,
1157
        null,
1158
        insertLinkAction,
1159
        insertImageAction,
1160
        null,
1161
        headers[ 0 ],
1162
        null,
1163
        insertUnorderedListAction,
1164
        insertOrderedListAction );
1165
1166
    return new VBox( menuBar, toolBar );
1167
  }
1168
1169
  /**
1170
   * Creates a boolean property that is bound to another boolean value of the
1171
   * active editor.
1172
   */
1173
  private BooleanProperty createActiveBooleanProperty(
1174
      final Function<FileEditorTab, ObservableBooleanValue> func ) {
1175
1176
    final BooleanProperty b = new SimpleBooleanProperty();
1177
    final FileEditorTab tab = getActiveFileEditorTab();
1178
1179
    if( tab != null ) {
1180
      b.bind( func.apply( tab ) );
1181
    }
1182
1183
    getFileEditorPane().activeFileEditorProperty().addListener(
1184
        ( observable, oldFileEditor, newFileEditor ) -> {
1185
          b.unbind();
1186
1187
          if( newFileEditor == null ) {
1188
            b.set( false );
1189
          }
1190
          else {
1191
            b.bind( func.apply( newFileEditor ) );
1192
          }
1193
        }
1194
    );
1195
1196
    return b;
1197
  }
1198
1199
  //---- Convenience accessors ----------------------------------------------
1200
1201
  private Preferences getPreferences() {
1202
    return OPTIONS.getState();
1203
  }
1204
1205
  private float getFloat( final String key, final float defaultValue ) {
1206
    return getPreferences().getFloat( key, defaultValue );
1207
  }
1208
1209
  public Window getWindow() {
1210
    return getScene().getWindow();
1211
  }
1212
1213
  private MarkdownEditorPane getActiveEditorPane() {
1214
    return getActiveFileEditorTab().getEditorPane();
1215
  }
1216
1217
  private FileEditorTab getActiveFileEditorTab() {
1218
    return getFileEditorPane().getActiveFileEditor();
1219
  }
1220
1221
  //---- Member accessors ---------------------------------------------------
1222
1223
  protected Scene getScene() {
1224
    return mScene;
1225
  }
1226
1227
  private Map<FileEditorTab, Processor<String>> getProcessors() {
1228
    return mProcessors;
1229
  }
1230
1231
  private FileEditorTabPane getFileEditorPane() {
1232
    return mFileEditorPane;
1233
  }
1234
1235
  private HTMLPreviewPane getPreviewPane() {
1236
    return mPreviewPane;
1237
  }
1238
1239
  private void setDefinitionSource(
1240
      final DefinitionSource definitionSource ) {
1241
    assert definitionSource != null;
1242
    mDefinitionSource = definitionSource;
1243
  }
1244
1245
  private DefinitionSource getDefinitionSource() {
1246
    return mDefinitionSource;
1247
  }
1248
1249
  private DefinitionPane getDefinitionPane() {
1250
    return mDefinitionPane;
1251
  }
1252
1253
  private Text getLineNumberText() {
1254
    return mLineNumberText;
1255
  }
1256
1257
  private StatusBar getStatusBar() {
1258
    return mStatusBar;
1259
  }
1260
1261
  private TextField getFindTextField() {
1262
    return mFindTextField;
1263
  }
1264
1265
  private VariableNameInjector getVariableNameInjector() {
1266
    return mVariableNameInjector;
1267
  }
1268
1269
  /**
1270
   * Returns the variable map of interpolated definitions.
1271
   *
1272
   * @return A map to help dereference variables.
1273
   */
1274
  private Map<String, String> getResolvedMap() {
1275
    return mResolvedMap;
1276
  }
1277
1278
  private Notifier getNotifier() {
1279
    return NOTIFIER;
1280
  }
1281
1282
  //---- Persistence accessors ----------------------------------------------
1283
1284
  private UserPreferences getUserPreferences() {
1285
    return OPTIONS.getUserPreferences();
1286
  }
1287
1288
  private Path getDefinitionPath() {
1289
    return getUserPreferences().getDefinitionPath();
46
import com.scrivenvar.spelling.api.SpellChecker;
47
import com.scrivenvar.spelling.impl.PermissiveSpeller;
48
import com.scrivenvar.spelling.impl.SymSpellSpeller;
49
import com.scrivenvar.util.Action;
50
import com.scrivenvar.util.ActionBuilder;
51
import com.scrivenvar.util.ActionUtils;
52
import javafx.beans.binding.Bindings;
53
import javafx.beans.binding.BooleanBinding;
54
import javafx.beans.property.BooleanProperty;
55
import javafx.beans.property.SimpleBooleanProperty;
56
import javafx.beans.value.ChangeListener;
57
import javafx.beans.value.ObservableBooleanValue;
58
import javafx.beans.value.ObservableValue;
59
import javafx.collections.ListChangeListener.Change;
60
import javafx.collections.ObservableList;
61
import javafx.event.Event;
62
import javafx.event.EventHandler;
63
import javafx.geometry.Pos;
64
import javafx.scene.Node;
65
import javafx.scene.Scene;
66
import javafx.scene.control.*;
67
import javafx.scene.control.Alert.AlertType;
68
import javafx.scene.image.Image;
69
import javafx.scene.image.ImageView;
70
import javafx.scene.input.Clipboard;
71
import javafx.scene.input.ClipboardContent;
72
import javafx.scene.input.KeyEvent;
73
import javafx.scene.layout.BorderPane;
74
import javafx.scene.layout.VBox;
75
import javafx.scene.text.Text;
76
import javafx.stage.Window;
77
import javafx.stage.WindowEvent;
78
import javafx.util.Duration;
79
import org.apache.commons.lang3.SystemUtils;
80
import org.controlsfx.control.StatusBar;
81
import org.fxmisc.richtext.StyleClassedTextArea;
82
import org.fxmisc.richtext.model.StyleSpansBuilder;
83
import org.reactfx.value.Val;
84
import org.xhtmlrenderer.util.XRLog;
85
86
import java.io.BufferedReader;
87
import java.io.InputStreamReader;
88
import java.nio.file.Path;
89
import java.nio.file.Paths;
90
import java.util.*;
91
import java.util.concurrent.atomic.AtomicInteger;
92
import java.util.function.Function;
93
import java.util.prefs.Preferences;
94
import java.util.stream.Collectors;
95
96
import static com.scrivenvar.Constants.*;
97
import static com.scrivenvar.Messages.get;
98
import static com.scrivenvar.util.StageState.*;
99
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
100
import static java.nio.charset.StandardCharsets.UTF_8;
101
import static java.util.Collections.emptyList;
102
import static java.util.Collections.singleton;
103
import static javafx.application.Platform.runLater;
104
import static javafx.event.Event.fireEvent;
105
import static javafx.scene.input.KeyCode.ENTER;
106
import static javafx.scene.input.KeyCode.TAB;
107
import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
108
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward;
109
110
/**
111
 * Main window containing a tab pane in the center for file editors.
112
 */
113
public class MainWindow implements Observer {
114
  /**
115
   * The {@code OPTIONS} variable must be declared before all other variables
116
   * to prevent subsequent initializations from failing due to missing user
117
   * preferences.
118
   */
119
  private final static Options OPTIONS = Services.load( Options.class );
120
  private final static Snitch SNITCH = Services.load( Snitch.class );
121
  private final static Notifier NOTIFIER = Services.load( Notifier.class );
122
123
  private final Scene mScene;
124
  private final StatusBar mStatusBar;
125
  private final Text mLineNumberText;
126
  private final TextField mFindTextField;
127
  private final SpellChecker mSpellChecker;
128
129
  private final Object mMutex = new Object();
130
131
  /**
132
   * Prevents re-instantiation of processing classes.
133
   */
134
  private final Map<FileEditorTab, Processor<String>> mProcessors =
135
      new HashMap<>();
136
137
  private final Map<String, String> mResolvedMap =
138
      new HashMap<>( DEFAULT_MAP_SIZE );
139
140
  /**
141
   * Called when the definition data is changed.
142
   */
143
  private final EventHandler<TreeItem.TreeModificationEvent<Event>>
144
      mTreeHandler = event -> {
145
    exportDefinitions( getDefinitionPath() );
146
    interpolateResolvedMap();
147
    renderActiveTab();
148
  };
149
150
  /**
151
   * Called to switch to the definition pane when the user presses the TAB key.
152
   */
153
  private final EventHandler<? super KeyEvent> mTabKeyHandler =
154
      (EventHandler<KeyEvent>) event -> {
155
        if( event.getCode() == TAB ) {
156
          getDefinitionPane().requestFocus();
157
          event.consume();
158
        }
159
      };
160
161
  /**
162
   * Called to inject the selected item when the user presses ENTER in the
163
   * definition pane.
164
   */
165
  private final EventHandler<? super KeyEvent> mDefinitionKeyHandler =
166
      event -> {
167
        if( event.getCode() == ENTER ) {
168
          getVariableNameInjector().injectSelectedItem();
169
        }
170
      };
171
172
  private final ChangeListener<Integer> mCaretPositionListener =
173
      ( observable, oldPosition, newPosition ) -> {
174
        final FileEditorTab tab = getActiveFileEditorTab();
175
        final EditorPane pane = tab.getEditorPane();
176
        final StyleClassedTextArea editor = pane.getEditor();
177
178
        getLineNumberText().setText(
179
            get( STATUS_BAR_LINE,
180
                 editor.getCurrentParagraph() + 1,
181
                 editor.getParagraphs().size(),
182
                 editor.getCaretPosition()
183
            )
184
        );
185
      };
186
187
  private final ChangeListener<Integer> mCaretParagraphListener =
188
      ( observable, oldIndex, newIndex ) ->
189
          scrollToParagraph( newIndex, true );
190
191
  private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
192
  private final DefinitionPane mDefinitionPane = new DefinitionPane();
193
  private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
194
  private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
195
      mCaretPositionListener,
196
      mCaretParagraphListener );
197
198
  /**
199
   * Listens on the definition pane for double-click events.
200
   */
201
  private final VariableNameInjector mVariableNameInjector
202
      = new VariableNameInjector( mDefinitionPane );
203
204
  public MainWindow() {
205
    mStatusBar = createStatusBar();
206
    mLineNumberText = createLineNumberText();
207
    mFindTextField = createFindTextField();
208
    mScene = createScene();
209
    mSpellChecker = createSpellChecker();
210
211
    System.getProperties()
212
          .setProperty( "xr.util-logging.loggingEnabled", "true" );
213
    XRLog.setLoggingEnabled( true );
214
215
    initLayout();
216
    initFindInput();
217
    initSnitch();
218
    initDefinitionListener();
219
    initTabAddedListener();
220
    initTabChangedListener();
221
    initPreferences();
222
    initVariableNameInjector();
223
224
    NOTIFIER.addObserver( this );
225
  }
226
227
  private void initLayout() {
228
    final Scene appScene = getScene();
229
230
    appScene.getStylesheets().add( STYLESHEET_SCENE );
231
232
    // TODO: Apply an XML syntax highlighting for XML files.
233
//    appScene.getStylesheets().add( STYLESHEET_XML );
234
    appScene.windowProperty().addListener(
235
        ( observable, oldWindow, newWindow ) ->
236
            newWindow.setOnCloseRequest(
237
                e -> {
238
                  if( !getFileEditorPane().closeAllEditors() ) {
239
                    e.consume();
240
                  }
241
                }
242
            )
243
    );
244
  }
245
246
  /**
247
   * Initialize the find input text field to listen on F3, ENTER, and
248
   * ESCAPE key presses.
249
   */
250
  private void initFindInput() {
251
    final TextField input = getFindTextField();
252
253
    input.setOnKeyPressed( ( KeyEvent event ) -> {
254
      switch( event.getCode() ) {
255
        case F3:
256
        case ENTER:
257
          editFindNext();
258
          break;
259
        case F:
260
          if( !event.isControlDown() ) {
261
            break;
262
          }
263
        case ESCAPE:
264
          getStatusBar().setGraphic( null );
265
          getActiveFileEditorTab().getEditorPane().requestFocus();
266
          break;
267
      }
268
    } );
269
270
    // Remove when the input field loses focus.
271
    input.focusedProperty().addListener(
272
        ( focused, oldFocus, newFocus ) -> {
273
          if( !newFocus ) {
274
            getStatusBar().setGraphic( null );
275
          }
276
        }
277
    );
278
  }
279
280
  /**
281
   * Watch for changes to external files. In particular, this awaits
282
   * modifications to any XSL files associated with XML files being edited.
283
   * When
284
   * an XSL file is modified (external to the application), the snitch's ears
285
   * perk up and the file is reloaded. This keeps the XSL transformation up to
286
   * date with what's on the file system.
287
   */
288
  private void initSnitch() {
289
    SNITCH.addObserver( this );
290
  }
291
292
  /**
293
   * Listen for {@link FileEditorTabPane} to receive open definition file
294
   * event.
295
   */
296
  private void initDefinitionListener() {
297
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
298
        ( final ObservableValue<? extends Path> file,
299
          final Path oldPath, final Path newPath ) -> {
300
          // Indirectly refresh the resolved map.
301
          resetProcessors();
302
303
          openDefinitions( newPath );
304
305
          // Will create new processors and therefore a new resolved map.
306
          renderActiveTab();
307
        }
308
    );
309
  }
310
311
  /**
312
   * When tabs are added, hook the various change listeners onto the new
313
   * tab sothat the preview pane refreshes as necessary.
314
   */
315
  private void initTabAddedListener() {
316
    final FileEditorTabPane editorPane = getFileEditorPane();
317
318
    // Make sure the text processor kicks off when new files are opened.
319
    final ObservableList<Tab> tabs = editorPane.getTabs();
320
321
    // Update the preview pane on tab changes.
322
    tabs.addListener(
323
        ( final Change<? extends Tab> change ) -> {
324
          while( change.next() ) {
325
            if( change.wasAdded() ) {
326
              // Multiple tabs can be added simultaneously.
327
              for( final Tab newTab : change.getAddedSubList() ) {
328
                final FileEditorTab tab = (FileEditorTab) newTab;
329
330
                initTextChangeListener( tab );
331
                initTabKeyEventListener( tab );
332
                initScrollEventListener( tab );
333
                initSpellCheckListener( tab );
334
//              initSyntaxListener( tab );
335
              }
336
            }
337
          }
338
        }
339
    );
340
  }
341
342
  private void initTextChangeListener( final FileEditorTab tab ) {
343
    tab.addTextChangeListener(
344
        ( editor, oldValue, newValue ) -> {
345
          process( tab );
346
          scrollToParagraph( getCurrentParagraphIndex() );
347
        }
348
    );
349
  }
350
351
  /**
352
   * Ensure that the keyboard events are received when a new tab is added
353
   * to the user interface.
354
   *
355
   * @param tab The tab editor that can trigger keyboard events.
356
   */
357
  private void initTabKeyEventListener( final FileEditorTab tab ) {
358
    tab.addEventFilter( KeyEvent.KEY_PRESSED, mTabKeyHandler );
359
  }
360
361
  private void initScrollEventListener( final FileEditorTab tab ) {
362
    final var scrollPane = tab.getScrollPane();
363
    final var scrollBar = getPreviewPane().getVerticalScrollBar();
364
365
    // Before the drag handler can be attached, the scroll bar for the
366
    // text editor pane must be visible.
367
    final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
368
        runLater( () -> {
369
          if( newShow ) {
370
            final var handler = new ScrollEventHandler( scrollPane, scrollBar );
371
            handler.enabledProperty().bind( tab.selectedProperty() );
372
          }
373
        } );
374
375
    Val.flatMap( scrollPane.sceneProperty(), Scene::windowProperty )
376
       .flatMap( Window::showingProperty )
377
       .addListener( listener );
378
  }
379
380
  /**
381
   * Listen for changes to the any particular paragraph and perform a quick
382
   * spell check upon it. The style classes in the editor will be changed to
383
   * mark any spelling mistakes in the paragraph. The user may then interact
384
   * with any misspelled word (i.e., any piece of text that is marked) to
385
   * revise the spelling.
386
   *
387
   * @param tab The tab to spellcheck.
388
   */
389
  private void initSpellCheckListener( final FileEditorTab tab ) {
390
    final var editor = tab.getEditorPane().getEditor();
391
392
    // Use the plain text changes so that notifications of style changes
393
    // are suppressed.
394
    editor.plainTextChanges()
395
          .filter( p -> !p.isIdentity() ).subscribe( change -> {
396
397
      // Only perform a spell check on the current paragraph. The
398
      // entire document is processed once, when opened.
399
      final var offset = change.getPosition();
400
      final var position = editor.offsetToPosition( offset, Forward );
401
      final var paraId = position.getMajor();
402
      final var paragraph = editor.getParagraph( paraId );
403
      final var text = paragraph.getText();
404
405
      editor.clearStyle( paraId );
406
407
      final var builder = new StyleSpansBuilder<Collection<String>>();
408
      final var count = new AtomicInteger( 0 );
409
      final var runningIndex = new AtomicInteger( 0 );
410
411
      getSpellChecker().proofread( text, ( prevIndex, currIndex ) -> {
412
        builder.add( emptyList(), prevIndex - runningIndex.get() );
413
        builder.add( singleton( "spelling" ), currIndex - prevIndex );
414
        count.incrementAndGet();
415
        runningIndex.set( currIndex );
416
      } );
417
418
      if( count.get() > 0 ) {
419
        builder.add( emptyList(), text.length() - runningIndex.get() );
420
421
        final var spans = builder.create();
422
        editor.setStyleSpans( paraId, 0, spans );
423
      }
424
    } );
425
  }
426
427
  /**
428
   * Listen for new tab selection events.
429
   */
430
  private void initTabChangedListener() {
431
    final FileEditorTabPane editorPane = getFileEditorPane();
432
433
    // Update the preview pane changing tabs.
434
    editorPane.addTabSelectionListener(
435
        ( tabPane, oldTab, newTab ) -> {
436
          if( newTab == null ) {
437
            getPreviewPane().clear();
438
          }
439
440
          // If there was no old tab, then this is a first time load, which
441
          // can be ignored.
442
          if( oldTab != null ) {
443
            if( newTab != null ) {
444
              final FileEditorTab tab = (FileEditorTab) newTab;
445
              updateVariableNameInjector( tab );
446
              process( tab );
447
            }
448
          }
449
        }
450
    );
451
  }
452
453
  /**
454
   * Reloads the preferences from the previous session.
455
   */
456
  private void initPreferences() {
457
    initDefinitionPane();
458
    getFileEditorPane().initPreferences();
459
  }
460
461
  private void initVariableNameInjector() {
462
    updateVariableNameInjector( getActiveFileEditorTab() );
463
  }
464
465
  private int getCurrentParagraphIndex() {
466
    return getActiveEditorPane().getCurrentParagraphIndex();
467
  }
468
469
  private void scrollToParagraph( final int id ) {
470
    scrollToParagraph( id, false );
471
  }
472
473
  /**
474
   * @param id    The paragraph to scroll to, will be approximated if it doesn't
475
   *              exist.
476
   * @param force {@code true} means to force scrolling immediately, which
477
   *              should only be attempted when it is known that the document
478
   *              has been fully rendered. Otherwise the internal map of ID
479
   *              attributes will be incomplete and scrolling will flounder.
480
   */
481
  private void scrollToParagraph( final int id, final boolean force ) {
482
    synchronized( mMutex ) {
483
      final var previewPane = getPreviewPane();
484
      final var scrollPane = previewPane.getScrollPane();
485
      final int approxId = getActiveEditorPane().approximateParagraphId( id );
486
487
      if( force ) {
488
        previewPane.scrollTo( approxId );
489
      }
490
      else {
491
        previewPane.tryScrollTo( approxId );
492
      }
493
494
      scrollPane.repaint();
495
    }
496
  }
497
498
  private void updateVariableNameInjector( final FileEditorTab tab ) {
499
    getVariableNameInjector().addListener( tab );
500
  }
501
502
  /**
503
   * Called whenever the preview pane becomes out of sync with the file editor
504
   * tab. This can be called when the text changes, the caret paragraph
505
   * changes, or the file tab changes.
506
   *
507
   * @param tab The file editor tab that has been changed in some fashion.
508
   */
509
  private void process( final FileEditorTab tab ) {
510
    if( tab == null ) {
511
      return;
512
    }
513
514
    getPreviewPane().setPath( tab.getPath() );
515
516
    final Processor<String> processor = getProcessors().computeIfAbsent(
517
        tab, p -> createProcessors( tab )
518
    );
519
520
    try {
521
      processChain( processor, tab.getEditorText() );
522
    } catch( final Exception ex ) {
523
      error( ex );
524
    }
525
  }
526
527
  /**
528
   * Executes the processing chain, operating on the given string.
529
   *
530
   * @param handler The first processor in the chain to call.
531
   * @param text    The initial value of the text to process.
532
   * @return The final value of the text that was processed by the chain.
533
   */
534
  private String processChain( Processor<String> handler, String text ) {
535
    while( handler != null && text != null ) {
536
      text = handler.process( text );
537
      handler = handler.next();
538
    }
539
540
    return text;
541
  }
542
543
  private void renderActiveTab() {
544
    process( getActiveFileEditorTab() );
545
  }
546
547
  /**
548
   * Called when a definition source is opened.
549
   *
550
   * @param path Path to the definition source that was opened.
551
   */
552
  private void openDefinitions( final Path path ) {
553
    try {
554
      final DefinitionSource ds = createDefinitionSource( path );
555
      setDefinitionSource( ds );
556
      getUserPreferences().definitionPathProperty().setValue( path.toFile() );
557
      getUserPreferences().save();
558
559
      final Tooltip tooltipPath = new Tooltip( path.toString() );
560
      tooltipPath.setShowDelay( Duration.millis( 200 ) );
561
562
      final DefinitionPane pane = getDefinitionPane();
563
      pane.update( ds );
564
      pane.addTreeChangeHandler( mTreeHandler );
565
      pane.addKeyEventHandler( mDefinitionKeyHandler );
566
      pane.filenameProperty().setValue( path.getFileName().toString() );
567
      pane.setTooltip( tooltipPath );
568
569
      interpolateResolvedMap();
570
    } catch( final Exception e ) {
571
      error( e );
572
    }
573
  }
574
575
  private void exportDefinitions( final Path path ) {
576
    try {
577
      final DefinitionPane pane = getDefinitionPane();
578
      final TreeItem<String> root = pane.getTreeView().getRoot();
579
      final TreeItem<String> problemChild = pane.isTreeWellFormed();
580
581
      if( problemChild == null ) {
582
        getDefinitionSource().getTreeAdapter().export( root, path );
583
        getNotifier().clear();
584
      }
585
      else {
586
        final String msg = get(
587
            "yaml.error.tree.form", problemChild.getValue() );
588
        getNotifier().notify( msg );
589
      }
590
    } catch( final Exception e ) {
591
      error( e );
592
    }
593
  }
594
595
  private void interpolateResolvedMap() {
596
    final Map<String, String> treeMap = getDefinitionPane().toMap();
597
    final Map<String, String> map = new HashMap<>( treeMap );
598
    MapInterpolator.interpolate( map );
599
600
    getResolvedMap().clear();
601
    getResolvedMap().putAll( map );
602
  }
603
604
  private void initDefinitionPane() {
605
    openDefinitions( getDefinitionPath() );
606
  }
607
608
  /**
609
   * Called when an exception occurs that warrants the user's attention.
610
   *
611
   * @param e The exception with a message that the user should know about.
612
   */
613
  private void error( final Exception e ) {
614
    getNotifier().notify( e );
615
  }
616
617
  //---- File actions -------------------------------------------------------
618
619
  /**
620
   * Called when an {@link Observable} instance has changed. This is called
621
   * by both the {@link Snitch} service and the notify service. The @link
622
   * Snitch} service can be called for different file types, including
623
   * {@link DefinitionSource} instances.
624
   *
625
   * @param observable The observed instance.
626
   * @param value      The noteworthy item.
627
   */
628
  @Override
629
  public void update( final Observable observable, final Object value ) {
630
    if( value != null ) {
631
      if( observable instanceof Snitch && value instanceof Path ) {
632
        updateSelectedTab();
633
      }
634
      else if( observable instanceof Notifier && value instanceof String ) {
635
        updateStatusBar( (String) value );
636
      }
637
    }
638
  }
639
640
  /**
641
   * Updates the status bar to show the given message.
642
   *
643
   * @param s The message to show in the status bar.
644
   */
645
  private void updateStatusBar( final String s ) {
646
    runLater(
647
        () -> {
648
          final int index = s.indexOf( '\n' );
649
          final String message = s.substring(
650
              0, index > 0 ? index : s.length() );
651
652
          getStatusBar().setText( message );
653
        }
654
    );
655
  }
656
657
  /**
658
   * Called when a file has been modified.
659
   */
660
  private void updateSelectedTab() {
661
    runLater(
662
        () -> {
663
          // Brute-force XSLT file reload by re-instantiating all processors.
664
          resetProcessors();
665
          renderActiveTab();
666
        }
667
    );
668
  }
669
670
  /**
671
   * After resetting the processors, they will refresh anew to be up-to-date
672
   * with the files (text and definition) currently loaded into the editor.
673
   */
674
  private void resetProcessors() {
675
    getProcessors().clear();
676
  }
677
678
  //---- File actions -------------------------------------------------------
679
680
  private void fileNew() {
681
    getFileEditorPane().newEditor();
682
  }
683
684
  private void fileOpen() {
685
    getFileEditorPane().openFileDialog();
686
  }
687
688
  private void fileClose() {
689
    getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
690
  }
691
692
  /**
693
   * TODO: Upon closing, first remove the tab change listeners. (There's no
694
   * need to re-render each tab when all are being closed.)
695
   */
696
  private void fileCloseAll() {
697
    getFileEditorPane().closeAllEditors();
698
  }
699
700
  private void fileSave() {
701
    getFileEditorPane().saveEditor( getActiveFileEditorTab() );
702
  }
703
704
  private void fileSaveAs() {
705
    final FileEditorTab editor = getActiveFileEditorTab();
706
    getFileEditorPane().saveEditorAs( editor );
707
    getProcessors().remove( editor );
708
709
    try {
710
      process( editor );
711
    } catch( final Exception ex ) {
712
      getNotifier().notify( ex );
713
    }
714
  }
715
716
  private void fileSaveAll() {
717
    getFileEditorPane().saveAllEditors();
718
  }
719
720
  private void fileExit() {
721
    final Window window = getWindow();
722
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
723
  }
724
725
  //---- Edit actions -------------------------------------------------------
726
727
  /**
728
   * Transform the Markdown into HTML then copy that HTML into the copy
729
   * buffer.
730
   */
731
  private void copyHtml() {
732
    final var markdown = getActiveEditorPane().getText();
733
    final var processors = createProcessorFactory().createProcessors(
734
        getActiveFileEditorTab()
735
    );
736
737
    final var chain = processors.remove( HtmlPreviewProcessor.class );
738
739
    final String html = processChain( chain, markdown );
740
741
    final Clipboard clipboard = Clipboard.getSystemClipboard();
742
    final ClipboardContent content = new ClipboardContent();
743
    content.putString( html );
744
    clipboard.setContent( content );
745
  }
746
747
  /**
748
   * Used to find text in the active file editor window.
749
   */
750
  private void editFind() {
751
    final TextField input = getFindTextField();
752
    getStatusBar().setGraphic( input );
753
    input.requestFocus();
754
  }
755
756
  public void editFindNext() {
757
    getActiveFileEditorTab().searchNext( getFindTextField().getText() );
758
  }
759
760
  public void editPreferences() {
761
    getUserPreferences().show();
762
  }
763
764
  //---- Insert actions -----------------------------------------------------
765
766
  /**
767
   * Delegates to the active editor to handle wrapping the current text
768
   * selection with leading and trailing strings.
769
   *
770
   * @param leading  The string to put before the selection.
771
   * @param trailing The string to put after the selection.
772
   */
773
  private void insertMarkdown(
774
      final String leading, final String trailing ) {
775
    getActiveEditorPane().surroundSelection( leading, trailing );
776
  }
777
778
  private void insertMarkdown(
779
      final String leading, final String trailing, final String hint ) {
780
    getActiveEditorPane().surroundSelection( leading, trailing, hint );
781
  }
782
783
  //---- Help actions -------------------------------------------------------
784
785
  private void helpAbout() {
786
    final Alert alert = new Alert( AlertType.INFORMATION );
787
    alert.setTitle( get( "Dialog.about.title" ) );
788
    alert.setHeaderText( get( "Dialog.about.header" ) );
789
    alert.setContentText( get( "Dialog.about.content" ) );
790
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
791
    alert.initOwner( getWindow() );
792
793
    alert.showAndWait();
794
  }
795
796
  //---- Member creators ----------------------------------------------------
797
798
  private SpellChecker createSpellChecker() {
799
    try {
800
      final Collection<String> lexicon = readLexicon( "en.txt" );
801
      return SymSpellSpeller.forLexicon( lexicon );
802
    } catch( final Exception e ) {
803
      getNotifier().notify( e );
804
      return new PermissiveSpeller();
805
    }
806
  }
807
808
  /**
809
   * Factory to create processors that are suited to different file types.
810
   *
811
   * @param tab The tab that is subjected to processing.
812
   * @return A processor suited to the file type specified by the tab's path.
813
   */
814
  private Processor<String> createProcessors( final FileEditorTab tab ) {
815
    return createProcessorFactory().createProcessors( tab );
816
  }
817
818
  private ProcessorFactory createProcessorFactory() {
819
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
820
  }
821
822
  private HTMLPreviewPane createHTMLPreviewPane() {
823
    return new HTMLPreviewPane();
824
  }
825
826
  private DefinitionSource createDefaultDefinitionSource() {
827
    return new YamlDefinitionSource( getDefinitionPath() );
828
  }
829
830
  private DefinitionSource createDefinitionSource( final Path path ) {
831
    try {
832
      return createDefinitionFactory().createDefinitionSource( path );
833
    } catch( final Exception ex ) {
834
      error( ex );
835
      return createDefaultDefinitionSource();
836
    }
837
  }
838
839
  private TextField createFindTextField() {
840
    return new TextField();
841
  }
842
843
  private DefinitionFactory createDefinitionFactory() {
844
    return new DefinitionFactory();
845
  }
846
847
  private StatusBar createStatusBar() {
848
    return new StatusBar();
849
  }
850
851
  private Scene createScene() {
852
    final SplitPane splitPane = new SplitPane(
853
        getDefinitionPane().getNode(),
854
        getFileEditorPane().getNode(),
855
        getPreviewPane().getNode() );
856
857
    splitPane.setDividerPositions(
858
        getFloat( K_PANE_SPLIT_DEFINITION, .22f ),
859
        getFloat( K_PANE_SPLIT_EDITOR, .60f ),
860
        getFloat( K_PANE_SPLIT_PREVIEW, .18f ) );
861
862
    getDefinitionPane().prefHeightProperty()
863
                       .bind( splitPane.heightProperty() );
864
865
    final BorderPane borderPane = new BorderPane();
866
    borderPane.setPrefSize( 1280, 800 );
867
    borderPane.setTop( createMenuBar() );
868
    borderPane.setBottom( getStatusBar() );
869
    borderPane.setCenter( splitPane );
870
871
    final VBox statusBar = new VBox();
872
    statusBar.setAlignment( Pos.BASELINE_CENTER );
873
    statusBar.getChildren().add( getLineNumberText() );
874
    getStatusBar().getRightItems().add( statusBar );
875
876
    // Force preview pane refresh on Windows.
877
    if( SystemUtils.IS_OS_WINDOWS ) {
878
      splitPane.getDividers().get( 1 ).positionProperty().addListener(
879
          ( l, oValue, nValue ) -> runLater(
880
              () -> getPreviewPane().getScrollPane().repaint()
881
          )
882
      );
883
    }
884
885
    return new Scene( borderPane );
886
  }
887
888
  private Text createLineNumberText() {
889
    return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) );
890
  }
891
892
  private Node createMenuBar() {
893
    final BooleanBinding activeFileEditorIsNull =
894
        getFileEditorPane().activeFileEditorProperty().isNull();
895
896
    // File actions
897
    final Action fileNewAction = new ActionBuilder()
898
        .setText( "Main.menu.file.new" )
899
        .setAccelerator( "Shortcut+N" )
900
        .setIcon( FILE_ALT )
901
        .setAction( e -> fileNew() )
902
        .build();
903
    final Action fileOpenAction = new ActionBuilder()
904
        .setText( "Main.menu.file.open" )
905
        .setAccelerator( "Shortcut+O" )
906
        .setIcon( FOLDER_OPEN_ALT )
907
        .setAction( e -> fileOpen() )
908
        .build();
909
    final Action fileCloseAction = new ActionBuilder()
910
        .setText( "Main.menu.file.close" )
911
        .setAccelerator( "Shortcut+W" )
912
        .setAction( e -> fileClose() )
913
        .setDisable( activeFileEditorIsNull )
914
        .build();
915
    final Action fileCloseAllAction = new ActionBuilder()
916
        .setText( "Main.menu.file.close_all" )
917
        .setAction( e -> fileCloseAll() )
918
        .setDisable( activeFileEditorIsNull )
919
        .build();
920
    final Action fileSaveAction = new ActionBuilder()
921
        .setText( "Main.menu.file.save" )
922
        .setAccelerator( "Shortcut+S" )
923
        .setIcon( FLOPPY_ALT )
924
        .setAction( e -> fileSave() )
925
        .setDisable( createActiveBooleanProperty(
926
            FileEditorTab::modifiedProperty ).not() )
927
        .build();
928
    final Action fileSaveAsAction = new ActionBuilder()
929
        .setText( "Main.menu.file.save_as" )
930
        .setAction( e -> fileSaveAs() )
931
        .setDisable( activeFileEditorIsNull )
932
        .build();
933
    final Action fileSaveAllAction = new ActionBuilder()
934
        .setText( "Main.menu.file.save_all" )
935
        .setAccelerator( "Shortcut+Shift+S" )
936
        .setAction( e -> fileSaveAll() )
937
        .setDisable( Bindings.not(
938
            getFileEditorPane().anyFileEditorModifiedProperty() ) )
939
        .build();
940
    final Action fileExitAction = new ActionBuilder()
941
        .setText( "Main.menu.file.exit" )
942
        .setAction( e -> fileExit() )
943
        .build();
944
945
    // Edit actions
946
    final Action editCopyHtmlAction = new ActionBuilder()
947
        .setText( Messages.get( "Main.menu.edit.copy.html" ) )
948
        .setIcon( HTML5 )
949
        .setAction( e -> copyHtml() )
950
        .setDisable( activeFileEditorIsNull )
951
        .build();
952
953
    final Action editUndoAction = new ActionBuilder()
954
        .setText( "Main.menu.edit.undo" )
955
        .setAccelerator( "Shortcut+Z" )
956
        .setIcon( UNDO )
957
        .setAction( e -> getActiveEditorPane().undo() )
958
        .setDisable( createActiveBooleanProperty(
959
            FileEditorTab::canUndoProperty ).not() )
960
        .build();
961
    final Action editRedoAction = new ActionBuilder()
962
        .setText( "Main.menu.edit.redo" )
963
        .setAccelerator( "Shortcut+Y" )
964
        .setIcon( REPEAT )
965
        .setAction( e -> getActiveEditorPane().redo() )
966
        .setDisable( createActiveBooleanProperty(
967
            FileEditorTab::canRedoProperty ).not() )
968
        .build();
969
970
    final Action editCutAction = new ActionBuilder()
971
        .setText( Messages.get( "Main.menu.edit.cut" ) )
972
        .setAccelerator( "Shortcut+X" )
973
        .setIcon( CUT )
974
        .setAction( e -> getActiveEditorPane().cut() )
975
        .setDisable( activeFileEditorIsNull )
976
        .build();
977
    final Action editCopyAction = new ActionBuilder()
978
        .setText( Messages.get( "Main.menu.edit.copy" ) )
979
        .setAccelerator( "Shortcut+C" )
980
        .setIcon( COPY )
981
        .setAction( e -> getActiveEditorPane().copy() )
982
        .setDisable( activeFileEditorIsNull )
983
        .build();
984
    final Action editPasteAction = new ActionBuilder()
985
        .setText( Messages.get( "Main.menu.edit.paste" ) )
986
        .setAccelerator( "Shortcut+V" )
987
        .setIcon( PASTE )
988
        .setAction( e -> getActiveEditorPane().paste() )
989
        .setDisable( activeFileEditorIsNull )
990
        .build();
991
    final Action editSelectAllAction = new ActionBuilder()
992
        .setText( Messages.get( "Main.menu.edit.selectAll" ) )
993
        .setAccelerator( "Shortcut+A" )
994
        .setAction( e -> getActiveEditorPane().selectAll() )
995
        .setDisable( activeFileEditorIsNull )
996
        .build();
997
998
    final Action editFindAction = new ActionBuilder()
999
        .setText( "Main.menu.edit.find" )
1000
        .setAccelerator( "Ctrl+F" )
1001
        .setIcon( SEARCH )
1002
        .setAction( e -> editFind() )
1003
        .setDisable( activeFileEditorIsNull )
1004
        .build();
1005
    final Action editFindNextAction = new ActionBuilder()
1006
        .setText( "Main.menu.edit.find.next" )
1007
        .setAccelerator( "F3" )
1008
        .setIcon( null )
1009
        .setAction( e -> editFindNext() )
1010
        .setDisable( activeFileEditorIsNull )
1011
        .build();
1012
    final Action editPreferencesAction = new ActionBuilder()
1013
        .setText( "Main.menu.edit.preferences" )
1014
        .setAccelerator( "Ctrl+Alt+S" )
1015
        .setAction( e -> editPreferences() )
1016
        .build();
1017
1018
    // Insert actions
1019
    final Action insertBoldAction = new ActionBuilder()
1020
        .setText( "Main.menu.insert.bold" )
1021
        .setAccelerator( "Shortcut+B" )
1022
        .setIcon( BOLD )
1023
        .setAction( e -> insertMarkdown( "**", "**" ) )
1024
        .setDisable( activeFileEditorIsNull )
1025
        .build();
1026
    final Action insertItalicAction = new ActionBuilder()
1027
        .setText( "Main.menu.insert.italic" )
1028
        .setAccelerator( "Shortcut+I" )
1029
        .setIcon( ITALIC )
1030
        .setAction( e -> insertMarkdown( "*", "*" ) )
1031
        .setDisable( activeFileEditorIsNull )
1032
        .build();
1033
    final Action insertSuperscriptAction = new ActionBuilder()
1034
        .setText( "Main.menu.insert.superscript" )
1035
        .setAccelerator( "Shortcut+[" )
1036
        .setIcon( SUPERSCRIPT )
1037
        .setAction( e -> insertMarkdown( "^", "^" ) )
1038
        .setDisable( activeFileEditorIsNull )
1039
        .build();
1040
    final Action insertSubscriptAction = new ActionBuilder()
1041
        .setText( "Main.menu.insert.subscript" )
1042
        .setAccelerator( "Shortcut+]" )
1043
        .setIcon( SUBSCRIPT )
1044
        .setAction( e -> insertMarkdown( "~", "~" ) )
1045
        .setDisable( activeFileEditorIsNull )
1046
        .build();
1047
    final Action insertStrikethroughAction = new ActionBuilder()
1048
        .setText( "Main.menu.insert.strikethrough" )
1049
        .setAccelerator( "Shortcut+T" )
1050
        .setIcon( STRIKETHROUGH )
1051
        .setAction( e -> insertMarkdown( "~~", "~~" ) )
1052
        .setDisable( activeFileEditorIsNull )
1053
        .build();
1054
    final Action insertBlockquoteAction = new ActionBuilder()
1055
        .setText( "Main.menu.insert.blockquote" )
1056
        .setAccelerator( "Ctrl+Q" )
1057
        .setIcon( QUOTE_LEFT )
1058
        .setAction( e -> insertMarkdown( "\n\n> ", "" ) )
1059
        .setDisable( activeFileEditorIsNull )
1060
        .build();
1061
    final Action insertCodeAction = new ActionBuilder()
1062
        .setText( "Main.menu.insert.code" )
1063
        .setAccelerator( "Shortcut+K" )
1064
        .setIcon( CODE )
1065
        .setAction( e -> insertMarkdown( "`", "`" ) )
1066
        .setDisable( activeFileEditorIsNull )
1067
        .build();
1068
    final Action insertFencedCodeBlockAction = new ActionBuilder()
1069
        .setText( "Main.menu.insert.fenced_code_block" )
1070
        .setAccelerator( "Shortcut+Shift+K" )
1071
        .setIcon( FILE_CODE_ALT )
1072
        .setAction( e -> insertMarkdown(
1073
            "\n\n```\n",
1074
            "\n```\n\n",
1075
            get( "Main.menu.insert.fenced_code_block.prompt" ) ) )
1076
        .setDisable( activeFileEditorIsNull )
1077
        .build();
1078
    final Action insertLinkAction = new ActionBuilder()
1079
        .setText( "Main.menu.insert.link" )
1080
        .setAccelerator( "Shortcut+L" )
1081
        .setIcon( LINK )
1082
        .setAction( e -> getActiveEditorPane().insertLink() )
1083
        .setDisable( activeFileEditorIsNull )
1084
        .build();
1085
    final Action insertImageAction = new ActionBuilder()
1086
        .setText( "Main.menu.insert.image" )
1087
        .setAccelerator( "Shortcut+G" )
1088
        .setIcon( PICTURE_ALT )
1089
        .setAction( e -> getActiveEditorPane().insertImage() )
1090
        .setDisable( activeFileEditorIsNull )
1091
        .build();
1092
1093
    // Number of header actions (H1 ... H3)
1094
    final int HEADERS = 3;
1095
    final Action[] headers = new Action[ HEADERS ];
1096
1097
    for( int i = 1; i <= HEADERS; i++ ) {
1098
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
1099
      final String markup = String.format( "%n%n%s ", hashes );
1100
      final String text = "Main.menu.insert.header." + i;
1101
      final String accelerator = "Shortcut+" + i;
1102
      final String prompt = text + ".prompt";
1103
1104
      headers[ i - 1 ] = new ActionBuilder()
1105
          .setText( text )
1106
          .setAccelerator( accelerator )
1107
          .setIcon( HEADER )
1108
          .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) )
1109
          .setDisable( activeFileEditorIsNull )
1110
          .build();
1111
    }
1112
1113
    final Action insertUnorderedListAction = new ActionBuilder()
1114
        .setText( "Main.menu.insert.unordered_list" )
1115
        .setAccelerator( "Shortcut+U" )
1116
        .setIcon( LIST_UL )
1117
        .setAction( e -> insertMarkdown( "\n\n* ", "" ) )
1118
        .setDisable( activeFileEditorIsNull )
1119
        .build();
1120
    final Action insertOrderedListAction = new ActionBuilder()
1121
        .setText( "Main.menu.insert.ordered_list" )
1122
        .setAccelerator( "Shortcut+Shift+O" )
1123
        .setIcon( LIST_OL )
1124
        .setAction( e -> insertMarkdown(
1125
            "\n\n1. ", "" ) )
1126
        .setDisable( activeFileEditorIsNull )
1127
        .build();
1128
    final Action insertHorizontalRuleAction = new ActionBuilder()
1129
        .setText( "Main.menu.insert.horizontal_rule" )
1130
        .setAccelerator( "Shortcut+H" )
1131
        .setAction( e -> insertMarkdown(
1132
            "\n\n---\n\n", "" ) )
1133
        .setDisable( activeFileEditorIsNull )
1134
        .build();
1135
1136
    // Help actions
1137
    final Action helpAboutAction = new ActionBuilder()
1138
        .setText( "Main.menu.help.about" )
1139
        .setAction( e -> helpAbout() )
1140
        .build();
1141
1142
    //---- MenuBar ----
1143
    final Menu fileMenu = ActionUtils.createMenu(
1144
        get( "Main.menu.file" ),
1145
        fileNewAction,
1146
        fileOpenAction,
1147
        null,
1148
        fileCloseAction,
1149
        fileCloseAllAction,
1150
        null,
1151
        fileSaveAction,
1152
        fileSaveAsAction,
1153
        fileSaveAllAction,
1154
        null,
1155
        fileExitAction );
1156
1157
    final Menu editMenu = ActionUtils.createMenu(
1158
        get( "Main.menu.edit" ),
1159
        editCopyHtmlAction,
1160
        null,
1161
        editUndoAction,
1162
        editRedoAction,
1163
        null,
1164
        editCutAction,
1165
        editCopyAction,
1166
        editPasteAction,
1167
        editSelectAllAction,
1168
        null,
1169
        editFindAction,
1170
        editFindNextAction,
1171
        null,
1172
        editPreferencesAction );
1173
1174
    final Menu insertMenu = ActionUtils.createMenu(
1175
        get( "Main.menu.insert" ),
1176
        insertBoldAction,
1177
        insertItalicAction,
1178
        insertSuperscriptAction,
1179
        insertSubscriptAction,
1180
        insertStrikethroughAction,
1181
        insertBlockquoteAction,
1182
        insertCodeAction,
1183
        insertFencedCodeBlockAction,
1184
        null,
1185
        insertLinkAction,
1186
        insertImageAction,
1187
        null,
1188
        headers[ 0 ],
1189
        headers[ 1 ],
1190
        headers[ 2 ],
1191
        null,
1192
        insertUnorderedListAction,
1193
        insertOrderedListAction,
1194
        insertHorizontalRuleAction
1195
    );
1196
1197
    final Menu helpMenu = ActionUtils.createMenu(
1198
        get( "Main.menu.help" ),
1199
        helpAboutAction );
1200
1201
    final MenuBar menuBar = new MenuBar(
1202
        fileMenu,
1203
        editMenu,
1204
        insertMenu,
1205
        helpMenu );
1206
1207
    //---- ToolBar ----
1208
    final ToolBar toolBar = ActionUtils.createToolBar(
1209
        fileNewAction,
1210
        fileOpenAction,
1211
        fileSaveAction,
1212
        null,
1213
        editUndoAction,
1214
        editRedoAction,
1215
        editCutAction,
1216
        editCopyAction,
1217
        editPasteAction,
1218
        null,
1219
        insertBoldAction,
1220
        insertItalicAction,
1221
        insertSuperscriptAction,
1222
        insertSubscriptAction,
1223
        insertBlockquoteAction,
1224
        insertCodeAction,
1225
        insertFencedCodeBlockAction,
1226
        null,
1227
        insertLinkAction,
1228
        insertImageAction,
1229
        null,
1230
        headers[ 0 ],
1231
        null,
1232
        insertUnorderedListAction,
1233
        insertOrderedListAction );
1234
1235
    return new VBox( menuBar, toolBar );
1236
  }
1237
1238
  /**
1239
   * Creates a boolean property that is bound to another boolean value of the
1240
   * active editor.
1241
   */
1242
  private BooleanProperty createActiveBooleanProperty(
1243
      final Function<FileEditorTab, ObservableBooleanValue> func ) {
1244
1245
    final BooleanProperty b = new SimpleBooleanProperty();
1246
    final FileEditorTab tab = getActiveFileEditorTab();
1247
1248
    if( tab != null ) {
1249
      b.bind( func.apply( tab ) );
1250
    }
1251
1252
    getFileEditorPane().activeFileEditorProperty().addListener(
1253
        ( observable, oldFileEditor, newFileEditor ) -> {
1254
          b.unbind();
1255
1256
          if( newFileEditor == null ) {
1257
            b.set( false );
1258
          }
1259
          else {
1260
            b.bind( func.apply( newFileEditor ) );
1261
          }
1262
        }
1263
    );
1264
1265
    return b;
1266
  }
1267
1268
  //---- Convenience accessors ----------------------------------------------
1269
1270
  private Preferences getPreferences() {
1271
    return OPTIONS.getState();
1272
  }
1273
1274
  private float getFloat( final String key, final float defaultValue ) {
1275
    return getPreferences().getFloat( key, defaultValue );
1276
  }
1277
1278
  public Window getWindow() {
1279
    return getScene().getWindow();
1280
  }
1281
1282
  private MarkdownEditorPane getActiveEditorPane() {
1283
    return getActiveFileEditorTab().getEditorPane();
1284
  }
1285
1286
  private FileEditorTab getActiveFileEditorTab() {
1287
    return getFileEditorPane().getActiveFileEditor();
1288
  }
1289
1290
  //---- Member accessors ---------------------------------------------------
1291
1292
  protected Scene getScene() {
1293
    return mScene;
1294
  }
1295
1296
  private SpellChecker getSpellChecker() {
1297
    return mSpellChecker;
1298
  }
1299
1300
  private Map<FileEditorTab, Processor<String>> getProcessors() {
1301
    return mProcessors;
1302
  }
1303
1304
  private FileEditorTabPane getFileEditorPane() {
1305
    return mFileEditorPane;
1306
  }
1307
1308
  private HTMLPreviewPane getPreviewPane() {
1309
    return mPreviewPane;
1310
  }
1311
1312
  private void setDefinitionSource(
1313
      final DefinitionSource definitionSource ) {
1314
    assert definitionSource != null;
1315
    mDefinitionSource = definitionSource;
1316
  }
1317
1318
  private DefinitionSource getDefinitionSource() {
1319
    return mDefinitionSource;
1320
  }
1321
1322
  private DefinitionPane getDefinitionPane() {
1323
    return mDefinitionPane;
1324
  }
1325
1326
  private Text getLineNumberText() {
1327
    return mLineNumberText;
1328
  }
1329
1330
  private StatusBar getStatusBar() {
1331
    return mStatusBar;
1332
  }
1333
1334
  private TextField getFindTextField() {
1335
    return mFindTextField;
1336
  }
1337
1338
  private VariableNameInjector getVariableNameInjector() {
1339
    return mVariableNameInjector;
1340
  }
1341
1342
  /**
1343
   * Returns the variable map of interpolated definitions.
1344
   *
1345
   * @return A map to help dereference variables.
1346
   */
1347
  private Map<String, String> getResolvedMap() {
1348
    return mResolvedMap;
1349
  }
1350
1351
  private Notifier getNotifier() {
1352
    return NOTIFIER;
1353
  }
1354
1355
  //---- Persistence accessors ----------------------------------------------
1356
1357
  private UserPreferences getUserPreferences() {
1358
    return OPTIONS.getUserPreferences();
1359
  }
1360
1361
  private Path getDefinitionPath() {
1362
    return getUserPreferences().getDefinitionPath();
1363
  }
1364
1365
  //---- Resource accessors -------------------------------------------------
1366
1367
  @SuppressWarnings("SameParameterValue")
1368
  private Collection<String> readLexicon( final String filename )
1369
      throws Exception {
1370
    final var path = Paths.get( LEXICONS_DIRECTORY, filename ).toString();
1371
    final var classLoader = MainWindow.class.getClassLoader();
1372
1373
    try( final var resource = classLoader.getResourceAsStream( path ) ) {
1374
      assert resource != null;
1375
1376
      return new BufferedReader( new InputStreamReader( resource, UTF_8 ) )
1377
          .lines()
1378
          .collect( Collectors.toList() );
1379
    }
12901380
  }
12911381
}
M src/main/java/com/scrivenvar/controls/BrowseFileButton.java
4747
4848
/**
49
 * Button that opens a file chooser to select a local file for a URL in
50
 * markdown.
49
 * Button that opens a file chooser to select a local file for a URL.
5150
 */
5251
public class BrowseFileButton extends Button {
M src/main/java/com/scrivenvar/controls/EscapeTextField.java
3434
3535
/**
36
 * TextField that can escape/unescape characters for markdown.
36
 * Responsible for escaping/unescaping characters for markdown.
3737
 */
3838
public class EscapeTextField extends TextField {
M src/main/java/com/scrivenvar/decorators/RVariableDecorator.java
2929
3030
/**
31
 * Brackets variable names with <code>`r#</code> and <code>`</code>.
31
 * Brackets variable names with {@code `r#} and {@code `}.
3232
 */
3333
public class RVariableDecorator implements VariableDecorator {
M src/main/java/com/scrivenvar/definition/DocumentParser.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
128
package com.scrivenvar.definition;
229
D src/main/java/com/scrivenvar/dialogs/RScriptDialog.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.dialogs;
29
30
import javafx.application.Platform;
31
import javafx.geometry.Insets;
32
import javafx.scene.control.Label;
33
import javafx.scene.control.TextArea;
34
import javafx.scene.layout.GridPane;
35
import javafx.stage.Window;
36
37
import static com.scrivenvar.Messages.get;
38
import static javafx.scene.control.ButtonType.OK;
39
40
/**
41
 * Responsible for managing the R startup script that is run when an R source
42
 * file is loaded.
43
 */
44
public class RScriptDialog extends AbstractDialog<String> {
45
46
  private TextArea mScriptArea;
47
  private final String mOriginalText;
48
49
  public RScriptDialog(
50
      final Window parent, final String title, final String script ) {
51
    super( parent, title );
52
    mOriginalText = script;
53
    getScriptArea().setText( script );
54
  }
55
56
  @Override
57
  protected void initComponents() {
58
    final GridPane grid = new GridPane();
59
    grid.setHgap( 10 );
60
    grid.setVgap( 10 );
61
    grid.setPadding( new Insets( 10, 10, 10, 10 ) );
62
63
    final Label label = new Label( get( "Dialog.r.script.content" ) );
64
65
    final TextArea textArea = getScriptArea();
66
    textArea.setEditable( true );
67
    textArea.setWrapText( true );
68
69
    grid.add( label, 0, 0 );
70
    grid.add( textArea, 0, 1 );
71
72
    getDialogPane().setContent( grid );
73
74
    Platform.runLater( textArea::requestFocus );
75
76
    setResultConverter(
77
        dialogButton -> dialogButton == OK ?
78
            textArea.getText() :
79
            getOriginalText()
80
    );
81
  }
82
83
  private TextArea getScriptArea() {
84
    if( mScriptArea == null ) {
85
      mScriptArea = new TextArea();
86
    }
87
88
    return mScriptArea;
89
  }
90
91
  private String getOriginalText() {
92
    return mOriginalText;
93
  }
94
}
951
M src/main/java/com/scrivenvar/editors/markdown/HyperlinkModel.java
3131
3232
/**
33
 * Represents the model for a hyperlink: text and url text.
33
 * Represents the model for a hyperlink: text, url, and title.
3434
 */
3535
public class HyperlinkModel {
...
4444
   *
4545
   * @param text The hyperlink text displayed (e.g., displayed to the user).
46
   * @param url The destination URL (e.g., when clicked).
46
   * @param url  The destination URL (e.g., when clicked).
4747
   */
4848
  public HyperlinkModel( final String text, final String url ) {
4949
    this( text, url, null );
5050
  }
5151
5252
  /**
5353
   * Constructs a new hyperlink model for the given AST link.
54
   * 
54
   *
5555
   * @param link A markdown link.
5656
   */
5757
  public HyperlinkModel( final Link link ) {
5858
    this(
59
      link.getText().toString(),
60
      link.getUrl().toString(),
61
      link.getTitle().toString()
59
        link.getText().toString(),
60
        link.getUrl().toString(),
61
        link.getTitle().toString()
6262
    );
6363
  }
6464
6565
  /**
6666
   * Constructs a new hyperlink model in Markdown format by default.
6767
   *
68
   * @param text The hyperlink text displayed (e.g., displayed to the user).
69
   * @param url The destination URL (e.g., when clicked).
68
   * @param text  The hyperlink text displayed (e.g., displayed to the user).
69
   * @param url   The destination URL (e.g., when clicked).
7070
   * @param title The hyperlink title (e.g., shown as a tooltip).
7171
   */
72
  public HyperlinkModel( final String text, final String url, final String title ) {
72
  public HyperlinkModel( final String text, final String url,
73
                         final String title ) {
7374
    setText( text );
7475
    setUrl( url );
M src/main/java/com/scrivenvar/editors/markdown/LinkVisitor.java
3434
3535
/**
36
 * Visits hyperlinks in a document so that the user can edit the hyperlink
37
 * within a dialog.
36
 * Responsible for extracting a hyperlink from the document so that the user
37
 * can edit the link within a dialog.
3838
 */
3939
public class LinkVisitor {
M src/main/java/com/scrivenvar/editors/markdown/MarkdownEditorPane.java
5858
5959
/**
60
 * Markdown editor pane.
60
 * Provides the ability to edit a text document.
6161
 */
6262
public class MarkdownEditorPane extends EditorPane {
M src/main/java/com/scrivenvar/preferences/UserPreferences.java
4949
import static com.scrivenvar.Messages.get;
5050
51
/**
52
 * Responsible for user preferences that can be changed from the GUI. The
53
 * settings are displayed and persisted using {@link PreferencesFx}.
54
 */
5155
public class UserPreferences {
5256
  private final Settings SETTINGS = Services.load( Settings.class );
M src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
5454
import java.nio.file.Path;
5555
56
import static com.scrivenvar.Constants.PARAGRAPH_ID_PREFIX;
57
import static com.scrivenvar.Constants.STYLESHEET_PREVIEW;
56
import static com.scrivenvar.Constants.*;
5857
import static java.awt.Desktop.Action.BROWSE;
5958
import static java.awt.Desktop.getDesktop;
...
177176
  private final CustomImageLoader mImageLoader = new CustomImageLoader();
178177
179
  private Path mPath;
178
  private Path mPath = DEFAULT_DIRECTORY;
180179
181180
  /**
...
196195
        NO_OP_REPAINT_LISTENER, mImageLoader ) );
197196
198
    // Ensure fonts are always anti-aliased.
199197
    final var context = getSharedContext();
200198
    context.setReplacedElementFactory( factory );
M src/main/java/com/scrivenvar/preview/SVGRasterizer.java
5353
import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName;
5454
55
/**
56
 * Responsible for converting SVG images into rasterized PNG images.
57
 */
5558
public class SVGRasterizer {
5659
  private final static Notifier NOTIFIER = Services.load( Notifier.class );
M src/main/java/com/scrivenvar/preview/SVGReplacedElementFactory.java
4646
import static com.scrivenvar.preview.SVGRasterizer.rasterize;
4747
48
/**
49
 * Responsible for running {@link SVGRasterizer} on SVG images detected within
50
 * a document to transform them into rasterized versions.
51
 */
4852
public class SVGReplacedElementFactory
4953
    implements ReplacedElementFactory {
M src/main/java/com/scrivenvar/processors/InlineRProcessor.java
4040
import java.util.Map;
4141
42
import static com.scrivenvar.Constants.*;
42
import static com.scrivenvar.Constants.STATUS_PARSE_ERROR;
4343
import static com.scrivenvar.Messages.get;
4444
import static com.scrivenvar.decorators.RVariableDecorator.PREFIX;
...
7676
  private static final ScriptEngine ENGINE =
7777
      (new ScriptEngineManager()).getEngineByName( "Renjin" );
78
79
  private static final int PREFIX_LENGTH = PREFIX.length();
7880
7981
  /**
...
122124
  public String process( final String text ) {
123125
    final int length = text.length();
124
    final int prefixLength = PREFIX.length();
125126
126127
    // The * 2 is a wild guess at the ratio of R statements to the length
...
136137
137138
      // Jump to the start of the R statement.
138
      prevIndex = currIndex + prefixLength;
139
      prevIndex = currIndex + PREFIX_LENGTH;
139140
140141
      // Find the statement ending (`), without indexing past the text boundary.
M src/main/java/com/scrivenvar/processors/markdown/ImageLinkExtension.java
4646
4747
import java.io.File;
48
import java.io.FileNotFoundException;
4849
import java.nio.file.Path;
4950
...
6869
   * @return The new {@link ImageLinkExtension}, never {@code null}.
6970
   */
70
  public static ImageLinkExtension create( final Path path ) {
71
  public static ImageLinkExtension create( @NotNull final Path path ) {
7172
    return new ImageLinkExtension( path );
7273
  }
...
8283
  private class ImageLinkResolver implements LinkResolver {
8384
    private final UserPreferences mUserPref = getUserPreferences();
84
    private final String mImagePrefix =
85
        mUserPref.getImagesDirectory().toString();
86
    private final String mImageSuffixes = mUserPref.getImagesOrder();
85
    private final File mImagesUserPrefix = mUserPref.getImagesDirectory();
86
    private final String mImageExtensions = mUserPref.getImagesOrder();
8787
8888
    public ImageLinkResolver() {
8989
    }
9090
91
    // you can also set/clear/modify attributes through
92
    // ResolvedLink.getAttributes() and
93
    // ResolvedLink.getNonNullAttributes()
91
    /**
92
     * You can also set/clear/modify attributes through
93
     * {@link ResolvedLink#getAttributes()} and
94
     * {@link ResolvedLink#getNonNullAttributes()}.
95
     */
9496
    @NotNull
9597
    @Override
9698
    public ResolvedLink resolveLink(
9799
        @NotNull final Node node,
98100
        @NotNull final LinkResolverBasicContext context,
99101
        @NotNull final ResolvedLink link ) {
100102
      return node instanceof Image ? resolve( link ) : link;
101103
    }
102104
103
    @NotNull
104
    private ResolvedLink resolve( @NotNull final ResolvedLink link ) {
105
    private ResolvedLink resolve( final ResolvedLink link ) {
105106
      String url = link.getUrl();
106107
107108
      try {
108
        final String imageFile = format( "%s/%s", getImagePrefix(), url );
109
        final String suffixes = getImageSuffixes();
110
        final String editDir = getEditDirectory();
109
        final Path imagePrefix = getImagePrefix().toPath();
110
111
        // Path to the file being edited.
112
        Path editPath = getEditPath();
113
114
        // If there is no parent path to the file, it means the file has not
115
        // been saved. Default to using the value from the user's preferences.
116
        // The user's preferences will be defaulted to a the application's
117
        // starting directory.
118
        if( editPath == null ) {
119
          editPath = imagePrefix;
120
        }
121
        else {
122
          editPath = Path.of( editPath.toString(), imagePrefix.toString() );
123
        }
124
125
        final Path imagePathPrefix = Path.of( editPath.toString(), url );
126
        final String suffixes = getImageExtensions();
127
        boolean missing = true;
111128
112129
        for( final String ext : Splitter.on( ' ' ).split( suffixes ) ) {
113
          final String imagePath = format(
114
              "%s/%s.%s", editDir, imageFile, ext );
130
          final String imagePath = format( "%s.%s", imagePathPrefix, ext );
115131
          final File file = new File( imagePath );
116132
117133
          if( file.exists() ) {
118134
            url = file.toString();
135
            missing = false;
119136
            break;
120137
          }
138
        }
139
140
        if( missing ) {
141
          throw new FileNotFoundException( imagePathPrefix + ".*" );
121142
        }
122143
123144
        final String protocol = ProtocolResolver.getProtocol( url );
124145
        if( "file".equals( protocol ) ) {
125146
          url = "file://" + url;
126147
        }
148
149
        getNotifier().clear();
127150
128151
        return link.withStatus( LinkStatus.VALID ).withUrl( url );
129152
      } catch( final Exception e ) {
130
        getNotifier().notify( e );
153
        getNotifier().notify( "File not found: " + e.getLocalizedMessage() );
131154
      }
132155
133156
      return link;
134157
    }
135158
136
    private String getImagePrefix() {
137
      return mImagePrefix;
159
    private File getImagePrefix() {
160
      return mImagesUserPrefix;
138161
    }
139162
140
    private String getImageSuffixes() {
141
      return mImageSuffixes;
163
    private String getImageExtensions() {
164
      return mImageExtensions;
142165
    }
143166
144
    private String getEditDirectory() {
145
      return mPath.getParent().toString();
167
    private Path getEditPath() {
168
      return mPath.getParent();
146169
    }
147170
  }
148171
149172
  private final Path mPath;
150173
151
  private ImageLinkExtension( final Path path ) {
174
  private ImageLinkExtension( @NotNull final Path path ) {
152175
    mPath = path;
153176
  }
M src/main/java/com/scrivenvar/service/Settings.java
8585
   */
8686
  List<String> getStringSettingList( String property );
87
88
  /**
89
   * Changes key's value. This will clear the old value before setting the
90
   * new value so that the old value is erased, not changed into a list.
91
   *
92
   * @param key   The property key name to obtain its value.
93
   * @param value The new value to set.
94
   */
95
  void putSetting( String key, String value );
9687
}
9788
M src/main/java/com/scrivenvar/service/impl/DefaultSettings.java
8282
8383
  /**
84
   * Changes key's value. This will clear the old value before setting the new
85
   * value so that the old value is erased, not changed into a list.
86
   *
87
   * @param key   The property key name to obtain its value.
88
   * @param value The new value to set.
89
   */
90
  @Override
91
  public void putSetting( final String key, final String value ) {
92
    getSettings().clearProperty( key );
93
    getSettings().addProperty( key, value );
94
  }
95
96
  /**
9784
   * Convert the generic list of property objects into strings.
9885
   *
A src/main/java/com/scrivenvar/spelling/api/SpellChecker.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.spelling.api;
29
30
import java.util.List;
31
import java.util.function.BiConsumer;
32
33
/**
34
 * Defines the responsibilities for a spell checking API. The intention is
35
 * to allow different spell checking implementations to be used by the
36
 * application, such as SymSpell and LinSpell.
37
 */
38
public interface SpellChecker {
39
40
  /**
41
   * Answers whether the given lexeme, in whole, is found in the lexicon. The
42
   * lexicon lookup is performed case-insensitively. This method should be
43
   * used instead of {@link #suggestions(String, int)} for performance reasons.
44
   *
45
   * @param lexeme The word to check for correctness.
46
   * @return {@code true} if the lexeme is in the lexicon.
47
   */
48
  boolean inLexicon( String lexeme );
49
50
  /**
51
   * Gets a list of spelling corrections for the given lexeme.
52
   *
53
   * @param lexeme A word to check for correctness that's not in the lexicon.
54
   * @param count  The maximum number of alternatives to return.
55
   * @return A list of words in the lexicon that are similar to the given
56
   * lexeme.
57
   */
58
  List<String> suggestions( String lexeme, int count );
59
60
  /**
61
   * Iterates over the given text, emitting starting and ending offsets into
62
   * the text for every word that is missing from the lexicon.
63
   *
64
   * @param text     The text to check for words missing from the lexicon.
65
   * @param consumer Every missing word emits a message with the starting
66
   *                 and ending offset into the text where said word is found.
67
   */
68
  void proofread( String text, BiConsumer<Integer, Integer> consumer );
69
}
170
A src/main/java/com/scrivenvar/spelling/impl/PermissiveSpeller.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.spelling.impl;
29
30
import com.scrivenvar.spelling.api.SpellChecker;
31
32
import java.util.List;
33
import java.util.function.BiConsumer;
34
35
/**
36
 * Responsible for spell checking in the event that a real spell checking
37
 * implementation cannot be created (for any reason). Does not perform any
38
 * spell checking and indicates that any given lexeme is in the lexicon.
39
 */
40
public class PermissiveSpeller implements SpellChecker {
41
  /**
42
   * Returns {@code true}, ignoring the given word.
43
   *
44
   * @param ignored Unused.
45
   * @return {@code true}
46
   */
47
  @Override
48
  public boolean inLexicon( final String ignored ) {
49
    return true;
50
  }
51
52
  /**
53
   * Returns an array with the given lexeme.
54
   *
55
   * @param lexeme  The word to return.
56
   * @param ignored Unused.
57
   * @return A suggestion list containing the given lexeme.
58
   */
59
  @Override
60
  public List<String> suggestions( final String lexeme, final int ignored ) {
61
    return List.of( lexeme );
62
  }
63
64
  /**
65
   * Performs no action.
66
   *
67
   * @param text    Unused.
68
   * @param ignored Uncalled.
69
   */
70
  @Override
71
  public void proofread(
72
      final String text, final BiConsumer<Integer, Integer> ignored ) {
73
  }
74
}
175
A src/main/java/com/scrivenvar/spelling/impl/SymSpellSpeller.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.spelling.impl;
29
30
import com.scrivenvar.spelling.api.SpellChecker;
31
import io.gitlab.rxp90.jsymspell.SuggestItem;
32
import io.gitlab.rxp90.jsymspell.SymSpell;
33
import io.gitlab.rxp90.jsymspell.SymSpellBuilder;
34
35
import java.text.BreakIterator;
36
import java.util.ArrayList;
37
import java.util.Collection;
38
import java.util.List;
39
import java.util.function.BiConsumer;
40
41
import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity;
42
import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity.ALL;
43
import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity.CLOSEST;
44
import static java.lang.Character.isLetter;
45
46
/**
47
 * Responsible for spell checking using {@link SymSpell}.
48
 */
49
public class SymSpellSpeller implements SpellChecker {
50
  private final SymSpell mSymSpell;
51
52
  /**
53
   * Creates a new lexicon for the given collection of lexemes.
54
   *
55
   * @param lexiconWords The words in the lexicon to add for spell checking,
56
   *                     must not be empty.
57
   * @return An instance of {@link SpellChecker} that can check if a word
58
   * is correct and suggest alternatives.
59
   */
60
  public static SpellChecker forLexicon(
61
      final Collection<String> lexiconWords ) {
62
    assert lexiconWords != null && !lexiconWords.isEmpty();
63
64
    final SymSpellBuilder builder = new SymSpellBuilder()
65
        .setLexiconWords( lexiconWords );
66
67
    return new SymSpellSpeller( builder.build() );
68
  }
69
70
  /**
71
   * Prevent direct instantiation so that only the {@link SpellChecker}
72
   * interface
73
   * is available.
74
   *
75
   * @param symSpell The implementation-specific spell checker.
76
   */
77
  private SymSpellSpeller( final SymSpell symSpell ) {
78
    mSymSpell = symSpell;
79
  }
80
81
  @Override
82
  public boolean inLexicon( final String lexeme ) {
83
    return lookup( lexeme, CLOSEST ).size() == 1;
84
  }
85
86
  @Override
87
  public List<String> suggestions( final String lexeme, int count ) {
88
    final List<String> result = new ArrayList<>( count );
89
90
    for( final var item : lookup( lexeme, ALL ) ) {
91
      if( count-- > 0 ) {
92
        result.add( item.getSuggestion() );
93
      }
94
      else {
95
        break;
96
      }
97
    }
98
99
    return result;
100
  }
101
102
  @Override
103
  public void proofread(
104
      final String text, final BiConsumer<Integer, Integer> consumer ) {
105
    assert text != null;
106
    assert consumer != null;
107
108
    final BreakIterator wb = BreakIterator.getWordInstance();
109
    wb.setText( text );
110
111
    int boundaryIndex = wb.first();
112
    int previousIndex = 0;
113
114
    while( boundaryIndex != BreakIterator.DONE ) {
115
      final String substring =
116
          text.substring( previousIndex, boundaryIndex ).toLowerCase();
117
118
      if( isWord( substring ) && !inLexicon( substring ) ) {
119
        consumer.accept( previousIndex, boundaryIndex );
120
      }
121
122
      previousIndex = boundaryIndex;
123
      boundaryIndex = wb.next();
124
    }
125
  }
126
127
  /**
128
   * Answers whether the given string is likely a word by checking the first
129
   * character.
130
   *
131
   * @param word The word to check.
132
   * @return {@code true} if the word begins with a letter.
133
   */
134
  private boolean isWord( final String word ) {
135
    return !word.isEmpty() && isLetter( word.charAt( 0 ) );
136
  }
137
138
  /**
139
   * Returns a list of {@link SuggestItem} instances that provide alternative
140
   * spellings for the given lexeme.
141
   *
142
   * @param lexeme A word to look up in the lexicon.
143
   * @param v      Influences the number of results returned.
144
   * @return Alternative lexemes.
145
   */
146
  private List<SuggestItem> lookup( final String lexeme, final Verbosity v ) {
147
    return getSpeller().lookup( lexeme, v );
148
  }
149
150
  private SymSpell getSpeller() {
151
    return mSymSpell;
152
  }
153
}
1154
M src/main/java/com/scrivenvar/util/Action.java
3434
3535
/**
36
 * Simple action class
36
 * Defines actions the user can take by interacting with the GUI.
3737
 */
3838
public class Action {
D src/main/java/com/scrivenvar/util/Lists.java
1
/*
2
 * Copyright 2020 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.util;
29
30
import java.util.List;
31
32
/**
33
 * Convenience class that provides a clearer API for obtaining list elements.
34
 */
35
public final class Lists {
36
37
  private Lists() {
38
  }
39
40
  /**
41
   * Returns the first item in the given list, or null if not found.
42
   *
43
   * @param <T> The generic list type.
44
   * @param list The list that may have a first item.
45
   *
46
   * @return null if the list is null or there is no first item.
47
   */
48
  public static <T> T getFirst( final List<T> list ) {
49
    return getFirst( list, null );
50
  }
51
52
  /**
53
   * Returns the last item in the given list, or null if not found.
54
   *
55
   * @param <T> The generic list type.
56
   * @param list The list that may have a last item.
57
   *
58
   * @return null if the list is null or there is no last item.
59
   */
60
  public static <T> T getLast( final List<T> list ) {
61
    return getLast( list, null );
62
  }
63
64
  /**
65
   * Returns the first item in the given list, or t if not found.
66
   *
67
   * @param <T> The generic list type.
68
   * @param list The list that may have a first item.
69
   * @param t The default return value.
70
   *
71
   * @return null if the list is null or there is no first item.
72
   */
73
  public static <T> T getFirst( final List<T> list, final T t ) {
74
    return isEmpty( list ) ? t : list.get( 0 );
75
  }
76
77
  /**
78
   * Returns the last item in the given list, or t if not found.
79
   *
80
   * @param <T> The generic list type.
81
   * @param list The list that may have a last item.
82
   * @param t The default return value.
83
   *
84
   * @return null if the list is null or there is no last item.
85
   */
86
  public static <T> T getLast( final List<T> list, final T t ) {
87
    return isEmpty( list ) ? t : list.get( list.size() - 1 );
88
  }
89
90
  /**
91
   * Returns true if the given list is null or empty.
92
   *
93
   * @param <T> The generic list type.
94
   * @param list The list that has a last item.
95
   *
96
   * @return true The list is empty.
97
   */
98
  public static <T> boolean isEmpty( final List<T> list ) {
99
    return list == null || list.isEmpty();
100
  }
101
}
1021
M src/main/resources/com/scrivenvar/editor/markdown.css
2828
2929
.markdown-editor {
30
  -fx-font-size: 14px;
30
  -fx-font-size: 11pt;
3131
}
3232
...
3939
.markdown-editor .selection {
4040
  -fx-fill: #a6d2ff;
41
}
42
43
/* Decoration for words not found in the lexicon. */
44
.markdown-editor .spelling {
45
  -rtfx-underline-color: rgba(255, 131, 67, .9);
46
  -rtfx-underline-dash-array: 4, 2;
47
  -rtfx-underline-width: 2;
4148
}
4249
M src/main/resources/com/scrivenvar/messages.properties
144144
145145
# ########################################################################
146
# Browse Directory
147
# ########################################################################
148
149
BrowseDirectoryButton.chooser.title=Browse for local folder
150
BrowseDirectoryButton.tooltip=${BrowseDirectoryButton.chooser.title}
151
152
# ########################################################################
153146
# Browse File
154147
# ########################################################################
...
188181
Dialog.about.header=${Main.title}
189182
Dialog.about.content=Copyright 2020 White Magic Software, Ltd.\n\nBased on Markdown Writer FX by Karl Tauber
190
191
# Options ################################################################
192
193
# ########################################################################
194
# Options Dialog
195
# ########################################################################
196
197
OptionsDialog.title=Options
198
OptionsDialog.generalTab.text=General
199
OptionsDialog.markdownTab.text=Markdown
200
201
# ########################################################################
202
# General Options Pane
203
# ########################################################################
204
205
GeneralOptionsPane.encodingLabel.text=En_coding\:
206
GeneralOptionsPane.lineSeparatorLabel.text=_Line separator\:
207
GeneralOptionsPane.lineSeparatorLabel2.text=(applies to new files only)
208
209
GeneralOptionsPane.platformDefault=Platform Default ({0})
210
GeneralOptionsPane.sepWindows=Windows (CRLF)
211
GeneralOptionsPane.sepUnix=Unix (LF)
212
213
# ########################################################################
214
# Markdown Options Pane
215
# ########################################################################
216
217
MarkdownOptionsPane.abbreviationsExtCheckBox.text=A_bbreviations in the way of
218
MarkdownOptionsPane.abbreviationsExtLink.text=Markdown Extra
219
MarkdownOptionsPane.anchorlinksExtCheckBox.text=_Anchor links in headers
220
MarkdownOptionsPane.atxHeaderSpaceExtCheckBox.text=Requires a space char after Atx \# header prefixes, so that \#dasdsdaf is not a header
221
MarkdownOptionsPane.autolinksExtCheckBox.text=_Plain (undelimited) autolinks in the way of
222
MarkdownOptionsPane.autolinksExtLink.text=Github-flavoured-Markdown
223
MarkdownOptionsPane.definitionListsExtCheckBox.text=_Definition lists in the way of
224
MarkdownOptionsPane.definitionListsExtLink.text=Markdown Extra
225
MarkdownOptionsPane.extAnchorLinksExtCheckBox.text=Generate anchor links for headers using complete contents of the header
226
MarkdownOptionsPane.fencedCodeBlocksExtCheckBox.text=_Fenced Code Blocks in the way of
227
MarkdownOptionsPane.fencedCodeBlocksExtLabel.text=or
228
MarkdownOptionsPane.fencedCodeBlocksExtLink.text=Markdown Extra
229
MarkdownOptionsPane.fencedCodeBlocksExtLink2.text=Github-flavoured-Markdown
230
MarkdownOptionsPane.forceListItemParaExtCheckBox.text=Force List and Definition Paragraph wrapping if it includes more than just a single paragraph
231
MarkdownOptionsPane.hardwrapsExtCheckBox.text=_Newlines in paragraph-like content as real line breaks, see
232
MarkdownOptionsPane.hardwrapsExtLink.text=Github-flavoured-Markdown
233
MarkdownOptionsPane.quotesExtCheckBox.text=Beautify single _quotes, double quotes and double angle quotes (\u00ab and \u00bb)
234
MarkdownOptionsPane.relaxedHrRulesExtCheckBox.text=Allow horizontal rules without a blank line following them
235
MarkdownOptionsPane.smartsExtCheckBox.text=Beautify apostrophes, _ellipses ("..." and ". . .") and dashes ("--" and "---")
236
MarkdownOptionsPane.strikethroughExtCheckBox.text=_Strikethrough
237
MarkdownOptionsPane.suppressHtmlBlocksExtCheckBox.text=Suppress the _output of HTML blocks
238
MarkdownOptionsPane.suppressInlineHtmlExtCheckBox.text=Suppress the o_utput of inline HTML elements
239
MarkdownOptionsPane.tablesExtCheckBox.text=_Tables similar to
240
MarkdownOptionsPane.tablesExtLabel.text=(like
241
MarkdownOptionsPane.tablesExtLabel2.text=tables, but with colspan support)
242
MarkdownOptionsPane.tablesExtLink.text=MultiMarkdown
243
MarkdownOptionsPane.tablesExtLink2.text=Markdown Extra
244
MarkdownOptionsPane.taskListItemsExtCheckBox.text=GitHub style task list items
245
MarkdownOptionsPane.wikilinksExtCheckBox.text=_Wiki-style links ("[[wiki link]]")
246183
A src/main/resources/lexicons/README.md
1
# Building
2
3
The lexicon files are retrieved from SymSpell in the parent directory:
4
5
svn export \
6
  https://github.com/wolfgarbe/SymSpell/trunk/SymSpell.FrequencyDictionary/ lexicons
7
8
The lexicons and bigrams are both space-separated, but parsing a
9
tab-delimited file is easier, so change them to tab-separated files.
110
A src/main/resources/lexicons/de.txt
Binary file
A src/main/resources/lexicons/en.txt
Binary file
A src/main/resources/lexicons/es.txt
Binary file
A src/main/resources/lexicons/fr.txt
Binary file
A src/main/resources/lexicons/he.txt
Binary file
A src/main/resources/lexicons/it.txt
Binary file
A src/main/resources/lexicons/ru.txt
Binary file
A src/main/resources/lexicons/zh.txt
Binary file