/*
* Copyright 2014 Corpuslinguistic working group Humboldt University Berlin.
*
* Licensed under the Apache License, Version 2.0 (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-2.0
*
* 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 annis.gui.components.codemirror;
import annis.model.AqlParseError;
import annis.model.QueryNode;
import com.vaadin.annotations.JavaScript;
import com.vaadin.annotations.StyleSheet;
import com.vaadin.data.Property;
import com.vaadin.data.util.ObjectProperty;
import com.vaadin.event.FieldEvents;
import com.vaadin.ui.AbstractJavaScriptComponent;
import com.vaadin.ui.JavaScriptFunction;
import elemental.json.JsonArray;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A code editor component for the ANNIQ Query Language.
*
* @author Thomas Krause <krauseto@hu-berlin.de>
*/
@JavaScript(
{
"vaadin://jquery.js",
"lib/codemirror.js",
"mode/aql/aql.js",
"lib/edit/matchbrackets.js",
"lib/lint/lint.js",
"lib/display/placeholder.js",
"AqlCodeEditor.js"
})
@StyleSheet(
{
"lib/codemirror.css",
"lib/lint/lint.css",
"AqlCodeEditor.css"
})
public class AqlCodeEditor extends AbstractJavaScriptComponent
implements FieldEvents.TextChangeNotifier, Property.Viewer,
Property.ValueChangeListener
{
private static final Logger log = LoggerFactory.getLogger(AqlCodeEditor.class);
private int timeout;
private Property<String> dataSource;
public AqlCodeEditor()
{
addFunction("textChanged", new TextChangedFunction());
addStyleName("aql-code-editor");
AqlCodeEditor.this.setPropertyDataSource(
new ObjectProperty("", String.class));
// init to one so the client (which starts with 0) at initialization always uses
// the the values provided by the server state
AqlCodeEditor.this.getState().serverRequestCounter = 1;
}
@Override
public void setPropertyDataSource(Property newDataSource)
{
if (newDataSource == null)
{
throw new IllegalArgumentException("Data source must not be null");
}
if (this.dataSource instanceof Property.ValueChangeNotifier)
{
((Property.ValueChangeNotifier) this.dataSource).
removeValueChangeListener(this);
}
this.dataSource = newDataSource;
if (newDataSource instanceof Property.ValueChangeNotifier)
{
((Property.ValueChangeNotifier) this.dataSource).
addValueChangeListener(this);
}
}
@Override
public Property getPropertyDataSource()
{
return this.dataSource;
}
@Override
public void valueChange(Property.ValueChangeEvent event)
{
log.debug("valueChange \"{}\"/\"{}", event.getProperty().getValue(),
this.dataSource.getValue());
String oldText = getState().text;
String newText = this.dataSource.getValue();
if (oldText == null || !oldText.equals(newText))
{
getState().text = newText;
// this is a server side state change and we have to explicitly tell the client we want to change the text
getState().serverRequestCounter++;
log.debug("invalidating \"{}\"/\"{}\"", oldText, getState().text);
markAsDirty();
}
}
private class TextChangedFunction implements JavaScriptFunction
{
@Override
public void call(JsonArray args) throws JSONException
{
log.debug("TextChangedFunction \"{}\"", args.getString(0));
getState().text = args.getString(0);
getPropertyDataSource().setValue(args.getString(0));
final String textCopy = dataSource.getValue();
final int cursorPos = (int) args.getNumber(1);
fireEvent(new FieldEvents.TextChangeEvent(AqlCodeEditor.this)
{
@Override
public String getText()
{
return textCopy;
}
@Override
public int getCursorPosition()
{
return cursorPos;
}
});
}
}
public void setNodes(List<QueryNode> nodes)
{
getState().nodeMappings.clear();
if(nodes != null)
{
getState().nodeMappings.putAll(mapQueryNodes(nodes));
}
}
private TreeMap<String, Integer> mapQueryNodes(List<QueryNode> nodes)
{
TreeMap<String, Integer> result = new TreeMap<>();
Map<Integer, TreeSet<Long>> alternative2Nodes = new HashMap<>();
for (QueryNode n : nodes)
{
TreeSet<Long> orderedNodeSet = alternative2Nodes.get(n.
getAlternativeNumber());
if (orderedNodeSet == null)
{
orderedNodeSet = new TreeSet<>();
alternative2Nodes.put(n.getAlternativeNumber(), orderedNodeSet);
}
orderedNodeSet.add(n.getId());
}
for (TreeSet<Long> orderedNodeSet : alternative2Nodes.values())
{
int newID = 1;
for (long var : orderedNodeSet)
{
result.put("" + var, newID);
newID++;
}
}
return result;
}
public void setInputPrompt(String prompt)
{
getState().inputPrompt = prompt;
markAsDirty();
}
public void setTextChangeTimeout(int timeout)
{
callFunction("setChangeDelayTime", timeout);
this.timeout = timeout;
}
public int getTextChangeTimeout()
{
return this.timeout;
}
@Override
public void addTextChangeListener(FieldEvents.TextChangeListener listener)
{
addListener(FieldEvents.TextChangeListener.EVENT_ID,
FieldEvents.TextChangeEvent.class,
listener, FieldEvents.TextChangeListener.EVENT_METHOD);
}
public String getTextareaStyle()
{
return getState().textareaClass == null ? "" : getState().textareaClass;
}
public void setTextareaStyle(String style)
{
getState().textareaClass = "".equals(style) ? null : style;
}
@Override
public void addListener(FieldEvents.TextChangeListener listener)
{
addTextChangeListener(listener);
}
@Override
public void removeTextChangeListener(FieldEvents.TextChangeListener listener)
{
removeListener(FieldEvents.TextChangeListener.EVENT_ID,
FieldEvents.TextChangeEvent.class,
listener);
}
@Override
public void removeListener(FieldEvents.TextChangeListener listener)
{
removeTextChangeListener(listener);
}
public String getValue()
{
return dataSource.getValue();
}
public void setValue(String value)
{
dataSource.setValue(value);
}
@Override
protected AqlCodeEditorState getState()
{
return (AqlCodeEditorState) super.getState();
}
public void setErrors(List<AqlParseError> errors)
{
getState().errors.clear();
if (errors != null)
{
for (AqlParseError e : errors)
{
getState().errors.add(new AqlCodeEditorState.ParseError(e));
}
}
markAsDirty();
}
}