1 /*******************************************************************************
\r
2 * Copyright (c) 2010, 2014 Association for Decentralized Information Management in
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.sysdyn.ui.properties.widgets.expressions;
\r
14 import java.util.ArrayList;
\r
15 import java.util.Collections;
\r
16 import java.util.Comparator;
\r
18 import org.eclipse.jface.resource.ImageDescriptor;
\r
19 import org.eclipse.jface.resource.JFaceResources;
\r
20 import org.eclipse.jface.resource.LocalResourceManager;
\r
21 import org.eclipse.jface.text.ITextViewer;
\r
22 import org.eclipse.jface.text.contentassist.CompletionProposal;
\r
23 import org.eclipse.jface.text.contentassist.ICompletionProposal;
\r
24 import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
\r
25 import org.eclipse.jface.text.contentassist.IContextInformation;
\r
26 import org.eclipse.jface.text.contentassist.IContextInformationValidator;
\r
27 import org.eclipse.swt.graphics.Image;
\r
28 import org.eclipse.swt.widgets.Control;
\r
29 import org.eclipse.swt.widgets.Table;
\r
30 import org.eclipse.swt.widgets.TableItem;
\r
31 import org.simantics.db.ReadGraph;
\r
32 import org.simantics.db.Resource;
\r
33 import org.simantics.db.exception.DatabaseException;
\r
34 import org.simantics.db.layer0.variable.Variables;
\r
35 import org.simantics.db.request.Read;
\r
36 import org.simantics.sysdyn.ui.Activator;
\r
37 import org.simantics.sysdyn.ui.properties.widgets.ShortcutTabWidget;
\r
38 import org.simantics.sysdyn.ui.utils.ExpressionUtils;
\r
39 import org.simantics.sysdyn.utils.Function;
\r
40 import org.simantics.ui.SimanticsUI;
\r
44 * IContentAssistProcessor to determine which options (the functions and
\r
45 * variables available) are shown for ContentAssistant; this assist of
\r
46 * text field allows long variable names to be selected from a popup menu.
\r
47 * @author Tuomas Miettinen
\r
50 public class CompletionProcessor implements IContentAssistProcessor {
\r
52 private final Table allowedVariables;
\r
53 private ArrayList<Function> functions;
\r
54 private ArrayList<String> variables = null;
\r
55 private ArrayList<String> timeAndSelfVariables = null;
\r
56 private final ExpressionWidgetInput input;
\r
58 private LocalResourceManager resourceManager;
\r
60 private static final char[] ALLOWED_CHARACTERS = {
\r
61 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','å','ä','ö',
\r
62 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','Å','Ä','Ö',
\r
63 '1','2','3','4','5','6','7','8','9','0','.','(',')'};
\r
65 private static final String ALLOWED_CONNECTED_CHARACTERS_REG_EXP = "[\\Q({[:;,<=>+-*/^\\E]";
\r
67 private static final ArrayList<String> ALLOW_ALL_COMPLETIONS_LIST = new ArrayList<String>();
\r
69 ALLOW_ALL_COMPLETIONS_LIST.add("");
\r
72 public CompletionProcessor(Table allowedVariables, boolean allowFunctions, ExpressionWidgetInput input) {
\r
73 this.allowedVariables = allowedVariables;
\r
75 this.functions = new ArrayList<Function>();
\r
77 if (allowFunctions) {
\r
78 if (input != null && CompletionProcessor.this.input.variable != null) {
\r
79 // Get the respective model
\r
80 Resource model = null;
\r
82 model = SimanticsUI.getSession().syncRequest(new Read<Resource>() {
\r
85 public Resource perform(ReadGraph graph) throws DatabaseException {
\r
86 return Variables.getModel(graph, CompletionProcessor.this.input.variable);
\r
90 //User defined functions
\r
91 functions.addAll(Function.getUserDefinedFunctions(model));
\r
93 functions.addAll(Function.getSharedFunctions(model));
\r
94 } catch (DatabaseException e) {
\r
95 e.printStackTrace();
\r
99 // Collect built in functions and sort all functions.
\r
100 functions.addAll(Function.getAllBuiltInFunctions());
\r
101 Collections.sort(functions);
\r
106 * Collect and sort all variables.
\r
108 private void findVariables() {
\r
109 if (variables == null) {
\r
110 variables = new ArrayList<String>();
\r
111 timeAndSelfVariables = new ArrayList<String>();
\r
112 if(allowedVariables != null && !allowedVariables.isDisposed()) {
\r
113 TableItem[] connectedVariables = allowedVariables.getItems();
\r
114 for(TableItem ti : connectedVariables) {
\r
115 // The status of the variable is determined using the color of its table item :(
\r
116 if (ExpressionUtils.variableTimeAndSelfColor(resourceManager).equals(ti.getForeground())) {
\r
117 this.timeAndSelfVariables.add(ti.getText());
\r
119 this.variables.add(ti.getText());
\r
124 Collections.sort(variables);
\r
125 Collections.sort(timeAndSelfVariables);
\r
130 * Create CompletionProposals of the variables and add them to array. Do not allow duplicates.
\r
131 * @param array result array of CompletionProposals
\r
132 * @param token current token
\r
133 * @param offset an offset within the document for which completions should be computed
\r
135 private void addVariables(ArrayList<ICompletionProposal> array, String token, int offset) {
\r
136 Image imageVariable = resourceManager.createImage(ImageDescriptor.createFromURL(Activator.getDefault().getBundle().getResource("icons/variable.png")));
\r
137 Image imageVariableGray = resourceManager.createImage(ImageDescriptor.createFromURL(Activator.getDefault().getBundle().getResource("icons/variableGray.png")));
\r
139 for (String variable : variables) {
\r
140 if ((token.length() == 0 || variable.toUpperCase().startsWith(token.toUpperCase()))
\r
141 && !listContainsVariable(array, variable)) {
\r
142 array.add(new CompletionProposal(variable,
\r
143 offset - token.length(),
\r
145 variable.length(),
\r
152 for (String variable : timeAndSelfVariables) {
\r
153 if ((token.length() == 0 || variable.toUpperCase().startsWith(token.toUpperCase()))
\r
154 && !listContainsVariable(array, variable)) {
\r
155 array.add(new CompletionProposal(variable,
\r
156 offset - token.length(),
\r
158 variable.length(),
\r
159 imageVariableGray,
\r
167 private boolean listContainsVariable(ArrayList<ICompletionProposal> array,
\r
169 for (ICompletionProposal proposal : array) {
\r
170 if (proposal.getDisplayString().equals(variable))
\r
177 * Create CompletionProposals of the functions and add them to array.
\r
178 * @param array result array of CompletionProposals
\r
179 * @param token current token
\r
180 * @param offset an offset within the document for which completions should be computed
\r
182 private void addFunctions(ArrayList<ICompletionProposal> array, String token, int offset) {
\r
183 // Parameters don't have functions
\r
184 if (functions == null)
\r
187 // Create CompletionProposals out of Functions
\r
188 for (Function function : functions) {
\r
189 if (token.length() == 0 || function.getName().toUpperCase().startsWith(token.toUpperCase())) {
\r
190 Image image = ShortcutTabWidget.getImage(resourceManager, function);
\r
191 String parameterList = Function.inputListToString(function.getInputList());
\r
192 array.add(new CompletionProposal(
\r
193 function.getName() + "(" + parameterList + ")",
\r
194 offset - token.length(),
\r
196 function.getName().length() + 1,
\r
198 function.getName() + "(" + parameterList + ")",
\r
200 function.getDescriptionHTML()));
\r
206 * Collect all matching proposals. Duplicates are removed; the one with the longest token stays.
\r
207 * @param possibleLabelBeginnings sets of whitespace delimited tokens (as Strings)
\r
208 * @param offset an offset within the document for which completions should be computed
\r
209 * @return Array of matching proposals
\r
211 private ICompletionProposal[] collectProposals(ArrayList<String> possibleLabelBeginnings, int offset) {
\r
212 ArrayList<ICompletionProposal> resultArray = new ArrayList<ICompletionProposal>();
\r
214 // Find variables and functions and create CompletionProposals out of them.
\r
217 // Sort the list based on the length of the tokens (descending) to get "" to end.
\r
218 Collections.sort(possibleLabelBeginnings, new Comparator<String>(){
\r
220 public int compare(String o1, String o2) {
\r
221 if (o1.length() > o2.length()) {
\r
223 } else if (o1.length() < o2.length()) {
\r
230 for (String possibleLabelBeginning : possibleLabelBeginnings) {
\r
231 addVariables(resultArray, possibleLabelBeginning, offset);
\r
234 // No support for whitespace in function names; get shortest beginning
\r
235 addFunctions(resultArray, possibleLabelBeginnings.get(possibleLabelBeginnings.size() - 1), offset);
\r
237 ICompletionProposal[] result = new ICompletionProposal[resultArray.size()];
\r
238 for (int i = 0; i < result.length; ++i) {
\r
239 result[i] = resultArray.get(i);
\r
245 public ICompletionProposal[] computeCompletionProposals(
\r
246 ITextViewer viewer, int offset) {
\r
247 String equation = viewer.getDocument().get();
\r
248 Control control = viewer.getTextWidget();
\r
249 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), control);
\r
251 if (equation.length() == 0 || offset == 0) {
\r
252 return collectProposals(ALLOW_ALL_COMPLETIONS_LIST, offset);
\r
255 equation = equation.substring(0, offset);
\r
257 // Split the equation on '+', '-', etc. characters
\r
258 String stringsBetweenConnectedCharacters[] = equation.split(ALLOWED_CONNECTED_CHARACTERS_REG_EXP);
\r
259 if (stringsBetweenConnectedCharacters.length == 0) {
\r
260 return collectProposals(ALLOW_ALL_COMPLETIONS_LIST, offset);
\r
262 String stringAfterLastConnectedCharacter = stringsBetweenConnectedCharacters[stringsBetweenConnectedCharacters.length - 1];
\r
263 String stringAfterWhitespaceAfterLastConnectedCharacter = removeLeadingWhitespace(stringAfterLastConnectedCharacter);
\r
265 // Split into tokens on whitespace characters, include also the trailing empty strings
\r
266 String[] tokens = stringAfterWhitespaceAfterLastConnectedCharacter.split("[\\s]", -42);
\r
268 // Only whitespace after the last connection character
\r
269 if (allTokensAreEmpty(tokens))
\r
270 return collectProposals(ALLOW_ALL_COMPLETIONS_LIST, offset);
\r
272 return collectProposals(getPossibleLabelBeginnings(tokens), offset);
\r
276 * Collect all possible strings (with each whitespace replaced by a space character)
\r
277 * which may be a beginning of a variable.
\r
278 * Create the beginnings by adding whitespace between. E.g.:
\r
279 * {"multi", "part", "variab"}
\r
280 * -> { "multi part variab",
\r
283 * @param tokens list of tokens
\r
284 * @return all possible label beginnings
\r
286 private ArrayList<String> getPossibleLabelBeginnings(String[] tokens) {
\r
287 ArrayList<String> possibleLabelBeginnings = new ArrayList<String>();
\r
288 for (int i = 0; i < tokens.length; ++i) {
\r
289 String token = new String();
\r
290 for (int j = i; j < tokens.length; ++j) {
\r
291 token += " " + tokens[j];
\r
293 // Remove the excess space character from the beginning
\r
294 token = token.substring(1);
\r
296 possibleLabelBeginnings.add(token);
\r
298 return possibleLabelBeginnings;
\r
302 * Remove leading whitespace
\r
306 private String removeLeadingWhitespace(String input) {
\r
307 for (int i = 0; i < input.length(); ++i) {
\r
308 if (!Character.isWhitespace(input.charAt(i))) {
\r
309 return input.substring(i);
\r
315 private boolean allTokensAreEmpty(String[] tokens) {
\r
316 for (String token : tokens)
\r
317 if (!token.equals(""))
\r
323 public IContextInformation[] computeContextInformation(
\r
324 ITextViewer viewer, int offset) {
\r
329 public char[] getCompletionProposalAutoActivationCharacters() {
\r
330 return ALLOWED_CHARACTERS;
\r
334 public char[] getContextInformationAutoActivationCharacters() {
\r
339 public String getErrorMessage() {
\r
340 return "Error in CompletionProcessor";
\r
344 public IContextInformationValidator getContextInformationValidator() {
\r