]> gerrit.simantics Code Review - simantics/sysdyn.git/blob
d2d78a52aa6588ec6088d219a8367a7c7afc15b4
[simantics/sysdyn.git] /
1 /*******************************************************************************\r
2  * Copyright (c) 2010, 2014 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\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
8  *\r
9  * Contributors:\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
13 \r
14 import java.util.ArrayList;\r
15 import java.util.Collections;\r
16 import java.util.Comparator;\r
17 \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
41 \r
42 \r
43 /**\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
48  *\r
49  */\r
50 public class CompletionProcessor implements IContentAssistProcessor {\r
51         \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
57     \r
58     private LocalResourceManager resourceManager;\r
59     \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
64             \r
65         private static final String ALLOWED_CONNECTED_CHARACTERS_REG_EXP = "[\\Q({[:;,<=>+-*/^\\E]";\r
66         \r
67         private static final ArrayList<String> ALLOW_ALL_COMPLETIONS_LIST = new ArrayList<String>();\r
68         static {\r
69                 ALLOW_ALL_COMPLETIONS_LIST.add("");\r
70         }\r
71         \r
72         public CompletionProcessor(Table allowedVariables, boolean allowFunctions, ExpressionWidgetInput input) {\r
73                 this.allowedVariables = allowedVariables;\r
74                 this.input = input;\r
75                 this.functions = new ArrayList<Function>();\r
76                 \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
81                     try {\r
82                     model = SimanticsUI.getSession().syncRequest(new Read<Resource>() {\r
83 \r
84                         @Override\r
85                         public Resource perform(ReadGraph graph) throws DatabaseException {\r
86                             return Variables.getModel(graph, CompletionProcessor.this.input.variable);\r
87                         }\r
88                     });\r
89                     \r
90                     //User defined functions\r
91                     functions.addAll(Function.getUserDefinedFunctions(model));\r
92                     // Shared functions\r
93                     functions.addAll(Function.getSharedFunctions(model));\r
94                 } catch (DatabaseException e) {\r
95                     e.printStackTrace();\r
96                 }\r
97                     }\r
98  \r
99                     // Collect built in functions and sort all functions.\r
100                 functions.addAll(Function.getAllBuiltInFunctions());\r
101             Collections.sort(functions);\r
102        }\r
103         }\r
104     \r
105         /**\r
106          * Collect and sort all variables.\r
107          */\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
118                     } else {\r
119                         this.variables.add(ti.getText());\r
120                     }\r
121                     \r
122                 }\r
123             }\r
124             Collections.sort(variables);\r
125             Collections.sort(timeAndSelfVariables);\r
126         }\r
127         }\r
128         \r
129         /**\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
134          */\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
138         \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
144                         token.length(), \r
145                         variable.length(), \r
146                         imageVariable, \r
147                         variable, \r
148                         null, \r
149                         null));\r
150             }   \r
151         }\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
157                         token.length(), \r
158                         variable.length(), \r
159                         imageVariableGray, \r
160                         variable, \r
161                         null, \r
162                         null));\r
163             }   \r
164         }\r
165         }\r
166         \r
167         private boolean listContainsVariable(ArrayList<ICompletionProposal> array,\r
168                         String variable) {\r
169                 for (ICompletionProposal proposal : array) {\r
170                         if (proposal.getDisplayString().equals(variable))\r
171                                 return true;\r
172                 }\r
173                 return false;\r
174         }\r
175 \r
176         /**\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
181      */\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
185                 return;\r
186             \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
195                         token.length(), \r
196                         function.getName().length() + 1,\r
197                         image, \r
198                         function.getName() + "(" + parameterList + ")", \r
199                         null, \r
200                         function.getDescriptionHTML()));\r
201             }   \r
202         }\r
203         }\r
204         \r
205         /**\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
210      */\r
211     private ICompletionProposal[] collectProposals(ArrayList<String> possibleLabelBeginnings, int offset) {\r
212             ArrayList<ICompletionProposal> resultArray = new ArrayList<ICompletionProposal>();\r
213         \r
214             // Find variables and functions and create CompletionProposals out of them.\r
215             findVariables();\r
216             \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
219                         @Override\r
220                         public int compare(String o1, String o2) {\r
221                                 if (o1.length() > o2.length()) {\r
222                                         return -1;\r
223                                 } else if (o1.length() < o2.length()) {\r
224                                         return 1;\r
225                                 }\r
226                                 return 0;\r
227                         }\r
228                 });\r
229             \r
230             for (String possibleLabelBeginning : possibleLabelBeginnings) {\r
231                 addVariables(resultArray, possibleLabelBeginning, offset);\r
232             }\r
233  \r
234             // No support for whitespace in function names; get shortest beginning\r
235         addFunctions(resultArray, possibleLabelBeginnings.get(possibleLabelBeginnings.size() - 1), offset);\r
236 \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
240                 }\r
241                 return result;\r
242         }\r
243         \r
244     @Override\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
250                 \r
251                 if (equation.length() == 0 || offset == 0) {\r
252                         return collectProposals(ALLOW_ALL_COMPLETIONS_LIST, offset);\r
253                 }\r
254                 \r
255                 equation = equation.substring(0, offset);\r
256                 \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
261                 }\r
262                 String stringAfterLastConnectedCharacter = stringsBetweenConnectedCharacters[stringsBetweenConnectedCharacters.length - 1];\r
263                 String stringAfterWhitespaceAfterLastConnectedCharacter = removeLeadingWhitespace(stringAfterLastConnectedCharacter);\r
264                                 \r
265                 // Split into tokens on whitespace characters, include also the trailing empty strings\r
266                 String[] tokens = stringAfterWhitespaceAfterLastConnectedCharacter.split("[\\s]", -42);\r
267                 \r
268                 // Only whitespace after the last connection character\r
269                 if (allTokensAreEmpty(tokens))\r
270                         return collectProposals(ALLOW_ALL_COMPLETIONS_LIST, offset);\r
271                         \r
272                 return collectProposals(getPossibleLabelBeginnings(tokens), offset);\r
273         }\r
274 \r
275         /**\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
281          *        "part variab",\r
282          *        "variab" }\r
283          * @param tokens list of tokens\r
284      * @return all possible label beginnings\r
285          */\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
292                         }\r
293                         // Remove the excess space character from the beginning\r
294                         token = token.substring(1);\r
295                         \r
296                         possibleLabelBeginnings.add(token);\r
297                 }\r
298                 return possibleLabelBeginnings;\r
299         }\r
300 \r
301         /**\r
302      * Remove leading whitespace\r
303      * @param input\r
304      * @return\r
305      */\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
310                         }\r
311                 }\r
312                 return "";\r
313         }\r
314 \r
315         private boolean allTokensAreEmpty(String[] tokens) {\r
316                 for (String token : tokens)\r
317                         if (!token.equals(""))\r
318                                 return false;\r
319                 return true;\r
320         }\r
321 \r
322         @Override\r
323         public IContextInformation[] computeContextInformation(\r
324                         ITextViewer viewer, int offset) {\r
325                 return null;\r
326         }\r
327 \r
328         @Override\r
329         public char[] getCompletionProposalAutoActivationCharacters() {\r
330                 return ALLOWED_CHARACTERS;\r
331         }\r
332 \r
333         @Override\r
334         public char[] getContextInformationAutoActivationCharacters() {\r
335                 return null;\r
336         }\r
337 \r
338         @Override\r
339         public String getErrorMessage() {\r
340                 return "Error in CompletionProcessor";\r
341         }\r
342 \r
343         @Override\r
344         public IContextInformationValidator getContextInformationValidator() {\r
345                 return null;\r
346         }\r
347 \r
348 }\r