mirror of
https://github.com/badvision/lawless-legends.git
synced 2024-08-22 22:28:58 +00:00
Converted text entry to a bigger area, allowing multi-line entry. Added rudimentary spell checking (it's not perfect at the momenent)
This commit is contained in:
parent
a4e87954a3
commit
8a88a147f2
@ -17,6 +17,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
@ -41,6 +42,9 @@ import org.badvision.outlaweditor.data.xml.Scope;
|
||||
import org.badvision.outlaweditor.data.xml.Script;
|
||||
import org.badvision.outlaweditor.data.xml.UserType;
|
||||
import org.badvision.outlaweditor.data.xml.Variable;
|
||||
import org.badvision.outlaweditor.spelling.SpellChecker;
|
||||
import org.badvision.outlaweditor.spelling.SpellResponse;
|
||||
import org.badvision.outlaweditor.spelling.Suggestion;
|
||||
import org.badvision.outlaweditor.ui.ApplicationUIController;
|
||||
import org.badvision.outlaweditor.ui.MythosScriptEditorController;
|
||||
import org.w3c.dom.Document;
|
||||
@ -58,10 +62,12 @@ public class MythosEditor {
|
||||
Stage primaryStage;
|
||||
MythosScriptEditorController controller;
|
||||
public static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
|
||||
SpellChecker spellChecker;
|
||||
|
||||
public MythosEditor(Script theScript, Scope theScope) {
|
||||
script = theScript;
|
||||
scope = theScope;
|
||||
spellChecker = new SpellChecker();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
@ -146,7 +152,6 @@ public class MythosEditor {
|
||||
// Called when the name of the root block is changed in the JS editor
|
||||
public void setFunctionName(String name) {
|
||||
if (script == null) {
|
||||
System.out.println("How can the script be null?? wanted to set script name to " + name);
|
||||
return;
|
||||
}
|
||||
script.setName(name);
|
||||
@ -165,9 +170,11 @@ public class MythosEditor {
|
||||
public List<Script> getGlobalFunctions() {
|
||||
return getFunctions(getGlobalScope());
|
||||
}
|
||||
|
||||
public List<Script> getLocalFunctions() {
|
||||
return getFunctions(scope);
|
||||
}
|
||||
|
||||
private List<Script> getFunctions(Scope scriptScope) {
|
||||
if (scriptScope.getScripts() == null) {
|
||||
return new ArrayList<>();
|
||||
@ -230,7 +237,25 @@ public class MythosEditor {
|
||||
return allArgs;
|
||||
}
|
||||
|
||||
public String checkSpelling(String value) {
|
||||
SpellResponse result = spellChecker.check(value);
|
||||
if (result.getErrors() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
StringBuilder message = new StringBuilder();
|
||||
result.getCorrections().forEach((SpellResponse.Source source, Set<Suggestion> suggestions) -> {
|
||||
message
|
||||
.append(source.word)
|
||||
.append(" : ")
|
||||
.append(suggestions.stream().map(Suggestion::getWord).collect(Collectors.joining(", ")))
|
||||
.append("\n");
|
||||
});
|
||||
return message.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public void log(String message) {
|
||||
Logger.getLogger(getClass().getName()).warning(message);
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,10 @@
|
||||
|
||||
package org.badvision.outlaweditor.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.badvision.outlaweditor.Application;
|
||||
import org.badvision.outlaweditor.data.xml.Field;
|
||||
@ -20,6 +24,8 @@ import org.badvision.outlaweditor.data.xml.Scope;
|
||||
import org.badvision.outlaweditor.data.xml.Script;
|
||||
|
||||
public class DataUtilities {
|
||||
private DataUtilities() {
|
||||
}
|
||||
|
||||
public static void ensureGlobalExists() {
|
||||
if (Application.gameData.getGlobal() == null) {
|
||||
@ -98,4 +104,130 @@ public class DataUtilities {
|
||||
cleanupScriptNames(Application.gameData.getGlobal());
|
||||
Application.gameData.getMap().forEach(DataUtilities::cleanupScriptNames);
|
||||
}
|
||||
|
||||
//------------------------------ String comparators
|
||||
/**
|
||||
* Rank two strings similarity in terms of distance The lower the number,
|
||||
* the more similar these strings are to each other See:
|
||||
* http://en.wikipedia.org/wiki/Levenshtein_distance#Computing_Levenshtein_distance
|
||||
*
|
||||
* @param s
|
||||
* @param t
|
||||
* @return Distance (higher is better)
|
||||
*/
|
||||
public static int levenshteinDistance(String s, String t) {
|
||||
if (s == null || t == null || s.length() == 0 || t.length() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
s = s.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
|
||||
t = t.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
|
||||
int m = s.length();
|
||||
int n = t.length();
|
||||
int[][] dist = new int[m + 1][n + 1];
|
||||
for (int i = 1; i <= m; i++) {
|
||||
dist[i][0] = i;
|
||||
}
|
||||
for (int i = 1; i <= n; i++) {
|
||||
dist[0][i] = i;
|
||||
}
|
||||
for (int j = 1; j <= n; j++) {
|
||||
for (int i = 1; i <= m; i++) {
|
||||
if (s.charAt(i - 1) == t.charAt(j - 1)) {
|
||||
dist[i][j] = dist[i - 1][j - 1];
|
||||
} else {
|
||||
int del = dist[i - 1][j] + 1;
|
||||
int insert = dist[i][j - 1] + 1;
|
||||
int sub = dist[i - 1][j - 1] + 1;
|
||||
dist[i][j] = Math.min(Math.min(del, insert), sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.max(m, n) - dist[m][n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare strings based on a tally of similar patterns found, using a fixed
|
||||
* search window The resulting score is heavily penalized if the strings
|
||||
* differ greatly in length This is not as efficient as levenshtein, so it's
|
||||
* only used as a tie-breaker.
|
||||
*
|
||||
* @param c1
|
||||
* @param c2
|
||||
* @param width Search window size
|
||||
* @return Overall similarity score (higher is beter)
|
||||
*/
|
||||
public static double rankMatch(String c1, String c2, int width) {
|
||||
double score = 0;
|
||||
String s1 = c1.toLowerCase();
|
||||
String s2 = c2.toLowerCase();
|
||||
for (int i = 0; i < s1.length() + 1 - width; i++) {
|
||||
String m = s1.substring(i, i + width);
|
||||
int j = 0;
|
||||
while ((j = s2.indexOf(m, j)) > -1) {
|
||||
score += width;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
double l1 = s1.length();
|
||||
double l2 = s2.length();
|
||||
// If the two strings are equivilent in length, the score is higher
|
||||
// If the two strings are different in length, the score is adjusted lower depending on how large the difference is
|
||||
// This is offset just a hair for tuning purposes
|
||||
double adjustment = (Math.min(l1, l2) / Math.max(l1, l2)) + 0.1;
|
||||
return score * adjustment * adjustment;
|
||||
}
|
||||
|
||||
public static class RankingComparator implements Comparator<String> {
|
||||
|
||||
String match;
|
||||
|
||||
public RankingComparator(String match) {
|
||||
// Adding a space helps respect word boundaries as part of the match
|
||||
// In the case of very close matches this is another tie-breaker
|
||||
// Especially for very small search terms
|
||||
this.match = match + " ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
double s1 = levenshteinDistance(match, o1);
|
||||
double s2 = levenshteinDistance(match, o2);
|
||||
if (s2 == s1) {
|
||||
s1 = rankMatch(o1, match, 3) + rankMatch(o1, match, 2);
|
||||
s2 = rankMatch(o2, match, 3) + rankMatch(o2, match, 2);
|
||||
if (s2 == s1) {
|
||||
return (o1.compareTo(o2));
|
||||
} else {
|
||||
// Normalize result to -1, 0 or 1 so there is no rounding issues!
|
||||
return (int) Math.signum(s2 - s1);
|
||||
}
|
||||
} else {
|
||||
return (int) (s2 - s1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a desired search string and a search space of recognized
|
||||
* selections, identify the best match in the list
|
||||
*
|
||||
* @param match String to search for
|
||||
* @param search Space of all valid results
|
||||
* @return Best match found, or null if there was nothing close to a match
|
||||
* found.
|
||||
*/
|
||||
public static String findBestMatch(String match, Collection<String> search) {
|
||||
if (search == null || search.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
RankingComparator r = new RankingComparator(match);
|
||||
List<String> candidates = new ArrayList<>(search);
|
||||
Collections.sort(candidates, r);
|
||||
double score = levenshteinDistance(match, candidates.get(0));
|
||||
if (score > 1) {
|
||||
return candidates.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1
|
||||
* (the "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
package org.badvision.outlaweditor.spelling;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.badvision.outlaweditor.data.DataUtilities;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author blurry
|
||||
*/
|
||||
public class SpellChecker {
|
||||
private static HashMap<Character, Set<String>> dictionary;
|
||||
private final double SIMILARITY_THRESHOLD = 0.5;
|
||||
|
||||
public SpellChecker() {
|
||||
loadDictionary();
|
||||
}
|
||||
|
||||
public SpellResponse check(String value) {
|
||||
SpellResponse response = new SpellResponse();
|
||||
String[] words = value.split("[^A-Za-z]");
|
||||
int pos = 0;
|
||||
for (String word : words) {
|
||||
Set<Suggestion> suggestions = getSuggestions(word);
|
||||
if (suggestions != null && !suggestions.isEmpty()) {
|
||||
Suggestion first = suggestions.stream().findFirst().get();
|
||||
if (first.similarity == 1.0) {
|
||||
continue;
|
||||
} else {
|
||||
SpellResponse.Source source = new SpellResponse.Source();
|
||||
source.start = pos;
|
||||
source.word = word;
|
||||
response.corrections.put(source, suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
pos += word.length() + 1;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private static void loadDictionary() {
|
||||
if (dictionary == null) {
|
||||
URL dictionaryPath = SpellChecker.class.getResource("/mythos/dictionary.txt");
|
||||
try {
|
||||
BufferedReader content = new BufferedReader(new InputStreamReader((InputStream) dictionaryPath.getContent()));
|
||||
dictionary = new HashMap<>();
|
||||
content.lines().forEach((String word)-> {
|
||||
String lower = word.toLowerCase();
|
||||
Set<String> words = dictionary.get(lower.charAt(0));
|
||||
if (words == null) {
|
||||
words = new LinkedHashSet<>();
|
||||
dictionary.put(lower.charAt(0), words);
|
||||
}
|
||||
words.add(word);
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(SpellChecker.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Suggestion> getSuggestions(String word) {
|
||||
TreeSet<Suggestion> suggestions = new TreeSet<>();
|
||||
if (word == null || word.isEmpty()) {
|
||||
return suggestions;
|
||||
}
|
||||
String lower = word.toLowerCase();
|
||||
Character first = lower.charAt(0);
|
||||
Set<String> words = dictionary.get(first);
|
||||
if (words != null) {
|
||||
if (words.contains(lower)) {
|
||||
return null;
|
||||
}
|
||||
words.parallelStream().forEach((String dictWord) -> {
|
||||
int distance = DataUtilities.levenshteinDistance(lower, dictWord);
|
||||
double similarity = distance / ((double) Math.max(lower.length(), dictWord.length()));
|
||||
if (similarity >= SIMILARITY_THRESHOLD) {
|
||||
Suggestion suggestion = new Suggestion();
|
||||
suggestion.similarity = similarity;
|
||||
suggestion.word = dictWord;
|
||||
suggestions.add(suggestion);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1
|
||||
* (the "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
package org.badvision.outlaweditor.spelling;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class SpellResponse {
|
||||
public static class Source {
|
||||
public int start;
|
||||
public String word;
|
||||
}
|
||||
|
||||
Map<Source, Set<Suggestion>> corrections = new LinkedHashMap<>();
|
||||
public int getErrors() {
|
||||
return corrections.size();
|
||||
}
|
||||
|
||||
public Map<Source, Set<Suggestion>> getCorrections() {
|
||||
return corrections;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1
|
||||
* (the "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
package org.badvision.outlaweditor.spelling;
|
||||
|
||||
public class Suggestion implements Comparable<Suggestion> {
|
||||
public String word;
|
||||
public double similarity;
|
||||
public String getWord() {
|
||||
return word;
|
||||
}
|
||||
public double getSimilarity() {
|
||||
return similarity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Suggestion o) {
|
||||
return (int) Math.signum(o.similarity - similarity);
|
||||
}
|
||||
}
|
274927
OutlawEditor/src/main/resources/mythos/dictionary.txt
Normal file
274927
OutlawEditor/src/main/resources/mythos/dictionary.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Blockly */
|
||||
/* global Blockly, goog */
|
||||
|
||||
if (typeof Mythos === "undefined") {
|
||||
// Hook up the rename function to notify the java editor when changes occur
|
||||
@ -20,7 +20,6 @@ if (typeof Mythos === "undefined") {
|
||||
Mythos.editor.setFunctionName(name);
|
||||
return Blockly.Procedures.rename_old.call(this, name);
|
||||
};
|
||||
|
||||
Mythos = {
|
||||
setScriptXml: function (xml) {
|
||||
Blockly.mainWorkspace.clear();
|
||||
@ -89,8 +88,7 @@ if (typeof Mythos === "undefined") {
|
||||
typeConstructor.appendValueInput(attribute.getName())
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.setCheck(attribute.getType())
|
||||
.appendField(attribute.getName())
|
||||
|
||||
.appendField(attribute.getName());
|
||||
});
|
||||
} catch (error) {
|
||||
Mythos.editor.log(error);
|
||||
@ -484,6 +482,63 @@ if (typeof Mythos === "undefined") {
|
||||
this.setTooltip('Scrolls text window up one line');
|
||||
}
|
||||
};
|
||||
Blockly.Blocks['text_area'] = {
|
||||
init: function () {
|
||||
var field = new Blockly.FieldTextArea('', this.checkSpelling);
|
||||
field.block_ = this;
|
||||
this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);
|
||||
this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.appendDummyInput()
|
||||
.appendField("Big")
|
||||
.appendField(this.newQuote_(true))
|
||||
.appendField(field, 'TEXT')
|
||||
.appendField(this.newQuote_(false));
|
||||
this.setOutput(true, 'String');
|
||||
this.setTooltip(Blockly.Msg.TEXT_TEXT_TOOLTIP);
|
||||
// this.setMutator(new Blockly.Mutator(['text']));
|
||||
},
|
||||
newQuote_: function (open) {
|
||||
var file;
|
||||
if (open === this.RTL) {
|
||||
file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==';
|
||||
} else {
|
||||
file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC';
|
||||
}
|
||||
return new Blockly.FieldImage(file, 12, 12, '"');
|
||||
},
|
||||
checkSpelling: function(value) {
|
||||
this.block_.setCommentText(Mythos.editor.checkSpelling(value));
|
||||
return value;
|
||||
}
|
||||
};
|
||||
Blockly.Blocks['text'] = {
|
||||
init: function () {
|
||||
var field = new Blockly.FieldTextInput('', this.checkSpelling);
|
||||
field.block_ = this;
|
||||
this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);
|
||||
this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.appendDummyInput()
|
||||
.appendField(this.newQuote_(true))
|
||||
.appendField(field, 'TEXT')
|
||||
.appendField(this.newQuote_(false));
|
||||
this.setOutput(true, 'String');
|
||||
this.setTooltip(Blockly.Msg.TEXT_TEXT_TOOLTIP);
|
||||
// this.setMutator(new Blockly.Mutator(['text_area']));
|
||||
},
|
||||
newQuote_: function (open) {
|
||||
var file;
|
||||
if (open === this.RTL) {
|
||||
file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==';
|
||||
} else {
|
||||
file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC';
|
||||
}
|
||||
return new Blockly.FieldImage(file, 12, 12, '"');
|
||||
},
|
||||
checkSpelling: function(value) {
|
||||
this.block_.setCommentText(Mythos.editor.checkSpelling(value));
|
||||
return value;
|
||||
}
|
||||
};
|
||||
Blockly.Blocks['text_getstring'] = {
|
||||
init: function () {
|
||||
this.setHelpUrl(Mythos.helpUrl);
|
||||
@ -553,3 +608,138 @@ if (typeof Mythos === "undefined") {
|
||||
};
|
||||
}
|
||||
;
|
||||
//------
|
||||
goog.provide('Blockly.FieldTextArea');
|
||||
goog.require('Blockly.Field');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
/**
|
||||
* Class for an editable text field.
|
||||
* @param {string} text The initial content of the field.
|
||||
* @param {Function} opt_changeHandler An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns either the accepted text, a replacement
|
||||
* text, or null to abort the change.FFF
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldTextArea = function (text, opt_changeHandler) {
|
||||
Blockly.FieldTextInput.superClass_.constructor.call(this, text);
|
||||
this.changeHandler_ = opt_changeHandler;
|
||||
};
|
||||
goog.inherits(Blockly.FieldTextArea, Blockly.FieldTextInput);
|
||||
/**
|
||||
* Clone this FieldTextArea.
|
||||
* @return {!Blockly.FieldTextArea} The result of calling the constructor again
|
||||
* with the current values of the arguments used during construction.
|
||||
*/
|
||||
Blockly.FieldTextArea.prototype.clone = function () {
|
||||
return new Blockly.FieldTextArea(this.getText(), this.changeHandler_);
|
||||
};
|
||||
/**
|
||||
* Show the inline free-text editor on top of the text.
|
||||
* @param {boolean=} opt_quietInput True if editor should be created without
|
||||
* focus. Defaults to false.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.showEditor_ = function (opt_quietInput) {
|
||||
var quietInput = opt_quietInput || false;
|
||||
if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID ||
|
||||
goog.userAgent.IPAD)) {
|
||||
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
||||
var newValue = window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_);
|
||||
if (this.sourceBlock_ && this.changeHandler_) {
|
||||
var override = this.changeHandler_(newValue);
|
||||
if (override !== undefined) {
|
||||
newValue = override;
|
||||
}
|
||||
}
|
||||
if (newValue !== null) {
|
||||
this.setText(newValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_());
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
// Create the input.
|
||||
var htmlInput = goog.dom.createDom('textarea', 'blocklyHtmlInput');
|
||||
htmlInput.setAttribute('spellcheck', this.spellcheck_);
|
||||
htmlInput.setAttribute('cols', 80);
|
||||
htmlInput.setAttribute('rows', 7);
|
||||
var fontSize = (Blockly.FieldTextInput.FONTSIZE *
|
||||
this.sourceBlock_.workspace.scale) + 'pt';
|
||||
div.style.fontSize = fontSize;
|
||||
div.style.width = "30em";
|
||||
htmlInput.style.width = "30em";
|
||||
htmlInput.style.fontSize = fontSize;
|
||||
htmlInput.style.backgroundColor = "#eee";
|
||||
/** @type {!HTMLTextArea} */
|
||||
Blockly.FieldTextInput.htmlInput_ = htmlInput;
|
||||
div.appendChild(htmlInput);
|
||||
|
||||
htmlInput.value = htmlInput.defaultValue = this.text_;
|
||||
htmlInput.oldValue_ = null;
|
||||
this.validate_();
|
||||
this.resizeEditor_();
|
||||
if (!quietInput) {
|
||||
htmlInput.focus();
|
||||
htmlInput.select();
|
||||
}
|
||||
|
||||
// Bind to keydown -- trap Enter without IME and Esc to hide.
|
||||
htmlInput.onKeyDownWrapper_ =
|
||||
Blockly.bindEvent_(htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
|
||||
// Bind to keyup -- trap Enter; resize after every keystroke.
|
||||
htmlInput.onKeyUpWrapper_ =
|
||||
Blockly.bindEvent_(htmlInput, 'keyup', this, this.onHtmlInputChange_);
|
||||
// Bind to keyPress -- repeatedly resize when holding down a key.
|
||||
htmlInput.onKeyPressWrapper_ =
|
||||
Blockly.bindEvent_(htmlInput, 'keypress', this, this.onHtmlInputChange_);
|
||||
var workspaceSvg = this.sourceBlock_.workspace.getCanvas();
|
||||
htmlInput.onWorkspaceChangeWrapper_ =
|
||||
Blockly.bindEvent_(workspaceSvg, 'blocklyWorkspaceChange', this,
|
||||
this.resizeEditor_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle key down to the editor.
|
||||
* @param {!Event} e Keyboard event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function (e) {
|
||||
var htmlInput = Blockly.FieldTextInput.htmlInput_;
|
||||
var escKey = 27;
|
||||
if (e.keyCode === escKey) {
|
||||
this.setText(htmlInput.defaultValue);
|
||||
Blockly.WidgetDiv.hide();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Handle a change to the editor.
|
||||
* @param {!Event} e Keyboard event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextArea.prototype.onHtmlInputChange_ = function (e) {
|
||||
var htmlInput = Blockly.FieldTextArea.htmlInput_;
|
||||
if (e.keyCode === 27) {
|
||||
// Esc
|
||||
this.setText(htmlInput.defaultValue);
|
||||
Blockly.WidgetDiv.hide();
|
||||
} else {
|
||||
// Update source block.
|
||||
var text = htmlInput.value;
|
||||
if (text !== htmlInput.oldValue_) {
|
||||
htmlInput.oldValue_ = text;
|
||||
this.setText(text);
|
||||
this.validate_();
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
// Cursor key. Render the source block to show the caret moving.
|
||||
// Chrome only (version 26, OS X).
|
||||
this.sourceBlock_.render();
|
||||
}
|
||||
this.resizeEditor_();
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user