1 package org.simantics.modeling.ui.componentTypeEditor;
3 import java.util.ArrayList;
4 import java.util.Collections;
6 import java.util.regex.Matcher;
7 import java.util.regex.Pattern;
9 import org.eclipse.jface.dialogs.IDialogConstants;
10 import org.eclipse.jface.dialogs.IMessageProvider;
11 import org.eclipse.jface.layout.GridDataFactory;
12 import org.eclipse.jface.layout.GridLayoutFactory;
13 import org.eclipse.osgi.util.NLS;
14 import org.eclipse.swt.SWT;
15 import org.eclipse.swt.custom.StyledText;
16 import org.eclipse.swt.custom.TableEditor;
17 import org.eclipse.swt.events.SelectionAdapter;
18 import org.eclipse.swt.events.SelectionEvent;
19 import org.eclipse.swt.graphics.Point;
20 import org.eclipse.swt.graphics.Rectangle;
21 import org.eclipse.swt.widgets.Button;
22 import org.eclipse.swt.widgets.Combo;
23 import org.eclipse.swt.widgets.Composite;
24 import org.eclipse.swt.widgets.Display;
25 import org.eclipse.swt.widgets.Event;
26 import org.eclipse.swt.widgets.Label;
27 import org.eclipse.swt.widgets.Shell;
28 import org.eclipse.swt.widgets.Table;
29 import org.eclipse.swt.widgets.TableItem;
30 import org.eclipse.swt.widgets.Text;
31 import org.eclipse.ui.forms.widgets.Form;
32 import org.eclipse.ui.forms.widgets.FormToolkit;
33 import org.simantics.Simantics;
34 import org.simantics.databoard.type.NumberType;
35 import org.simantics.databoard.units.internal.library.UnitLibrary;
36 import org.simantics.databoard.util.Limit;
37 import org.simantics.databoard.util.Range;
38 import org.simantics.databoard.util.RangeException;
39 import org.simantics.db.RequestProcessor;
40 import org.simantics.db.Resource;
41 import org.simantics.db.WriteGraph;
42 import org.simantics.db.common.NamedResource;
43 import org.simantics.db.common.request.WriteRequest;
44 import org.simantics.db.exception.DatabaseException;
45 import org.simantics.db.function.DbConsumer;
46 import org.simantics.layer0.Layer0;
47 import org.simantics.modeling.userComponent.ComponentTypeCommands;
48 import org.simantics.scl.runtime.function.Function2;
49 import org.simantics.scl.runtime.function.Function4;
50 import org.simantics.utils.ui.ErrorLogger;
52 public class ComponentTypeViewerData {
54 * Used to validate property names.
56 public static final Pattern PROPERTY_NAME_PATTERN =
57 Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*"); //$NON-NLS-1$
59 public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
60 "Double", //$NON-NLS-1$
61 "Integer", //$NON-NLS-1$
62 "Float", //$NON-NLS-1$
63 "String", //$NON-NLS-1$
64 "Boolean", //$NON-NLS-1$
66 "[Double]", //$NON-NLS-1$
67 "[Integer]", //$NON-NLS-1$
68 "[Float]", //$NON-NLS-1$
69 "[String]", //$NON-NLS-1$
70 "[Boolean]", //$NON-NLS-1$
71 "[Long]", //$NON-NLS-1$
72 "Vector Double", //$NON-NLS-1$
73 "Vector Integer", //$NON-NLS-1$
74 "Vector Float", //$NON-NLS-1$
75 "Vector String", //$NON-NLS-1$
76 "Vector Boolean", //$NON-NLS-1$
77 "Vector Long" //$NON-NLS-1$
80 public Resource componentType;
81 public FormToolkit tk;
83 public UnitLibrary unitLibrary = UnitLibrary.createDefault();
84 public boolean readOnly;
85 public NamedResource[] connectionPoints;
86 public ComponentTypeViewerPropertyInfo[] properties;
88 public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
90 this.componentType = componentType;
94 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
95 Pattern namePattern) {
96 editName(table, editor, propertyInfo, selectedItem, column, namePattern, null);
99 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
100 Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
101 editName(table, editor, propertyInfo, selectedItem, column,
103 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
107 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
108 Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter, Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
109 editName(table, editor, propertyInfo, selectedItem, column, nameFilter,
110 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
114 public void editName(Table table, TableEditor editor, ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
115 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator)
117 editName(table, editor, propertyInfo, selectedItem, column, nameValidator, null);
120 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
121 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator, DbConsumer<WriteGraph> extraWriter) {
122 editName(table, editor, propertyInfo, selectedItem, column, null, nameValidator, extraWriter);
125 public void editName(
128 final ComponentTypeViewerPropertyInfo propertyInfo,
129 TableItem selectedItem,
131 Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter,
132 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator,
133 DbConsumer<WriteGraph> extraWriter) {
134 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
135 final Text text = new Text(table, SWT.NONE | extraStyle);
136 org.eclipse.swt.widgets.Listener listener =
137 new org.eclipse.swt.widgets.Listener() {
139 public void handleEvent(Event e) {
140 if (e.type == SWT.Dispose) {
141 form.setMessage(null);
143 } else if (e.type == SWT.Verify) {
144 // Filter input if necessary
145 e.text = nameFilter != null ? nameFilter.apply(propertyInfo, e.text) : e.text;
147 } else if (e.type == SWT.Modify) {
148 // validate current name
149 String error = nameValidator.apply(propertyInfo, text.getText());
151 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
152 form.setMessage(error, IMessageProvider.ERROR);
154 text.setBackground(null);
155 form.setMessage(null);
158 } else if (e.type == SWT.Traverse) {
159 if (e.detail == SWT.TRAVERSE_ESCAPE) {
164 if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
168 final String newValue = text.getText();
171 String error = nameValidator.apply(propertyInfo, newValue);
175 if (propertyInfo.immutable)
178 Simantics.getSession().async(new WriteRequest() {
180 public void perform(WriteGraph graph)
181 throws DatabaseException {
182 graph.markUndoPoint();
183 Layer0 L0 = Layer0.getInstance(graph);
184 String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
185 String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : ""; //$NON-NLS-1$
186 String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
187 boolean setLabel = oldLabel == null
188 || oldLabel.isEmpty()
189 || oldCamelCasedLabel.isEmpty()
190 || oldCamelCasedLabel.equals(oldLabel);
192 ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
194 ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
196 if (extraWriter != null)
197 extraWriter.accept(graph);
202 if (nameFilter != null)
203 text.addListener(SWT.Verify, listener);
204 text.addListener(SWT.Modify, listener);
205 text.addListener(SWT.Deactivate, listener);
206 text.addListener(SWT.Traverse, listener);
207 text.addListener(SWT.Dispose, listener);
209 text.setText(selectedItem.getText(column));
213 editor.setEditor(text, selectedItem, column);
216 public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {
217 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
218 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
219 combo.setText(selectedItem.getText(column));
220 for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
221 combo.add(suggestion);
222 org.eclipse.swt.widgets.Listener listener =
223 new org.eclipse.swt.widgets.Listener() {
225 public void handleEvent(Event e) {
226 if(e.type == SWT.Traverse) {
227 if (e.detail == SWT.TRAVERSE_ESCAPE) {
232 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
233 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
234 || e.detail == SWT.TRAVERSE_MNEMONIC)
237 final String newValue = combo.getText();
238 if (e.type == SWT.Traverse) {
243 if (propertyInfo.immutable)
246 Simantics.getSession().async(new WriteRequest() {
248 public void perform(WriteGraph graph)
249 throws DatabaseException {
250 graph.markUndoPoint();
251 ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
257 editor.setEditor(combo, selectedItem, column);
258 combo.addListener(SWT.FocusOut, listener);
259 combo.addListener(SWT.Traverse, listener);
262 public void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
263 // Disallow unit editing for non-numeric configuration properties
264 if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
267 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
268 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
269 String initialValue = selectedItem.getText(column);
270 List<String> units = new ArrayList<>( unitLibrary.getUnits() );
271 Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
274 for (String unit : units) {
276 if (unit.equals(initialValue))
280 combo.setText(initialValue);
282 org.eclipse.swt.widgets.Listener listener =
283 new org.eclipse.swt.widgets.Listener() {
285 public void handleEvent(Event e) {
286 if(e.type == SWT.Traverse) {
287 if (e.detail == SWT.TRAVERSE_ESCAPE) {
292 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
293 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
294 || e.detail == SWT.TRAVERSE_MNEMONIC)
297 final String newValue = combo.getText();
298 if(e.type == SWT.Traverse) {
303 if (propertyInfo.immutable)
306 Simantics.getSession().async(new WriteRequest() {
308 public void perform(WriteGraph graph)
309 throws DatabaseException {
310 graph.markUndoPoint();
311 ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
317 editor.setEditor(combo, selectedItem, column);
318 combo.addListener(SWT.Deactivate, listener);
319 combo.addListener(SWT.Traverse, listener);
322 public void editValue(Table table, TableEditor editor,
323 final ComponentTypeViewerPropertyInfo propertyInfo,
324 TableItem selectedItem, int column,
325 final StringWriter writer,
326 final Function4<RequestProcessor, Resource, Resource, String, String> validator)
328 int extraStyle = writer == null ? SWT.READ_ONLY : 0;
329 final Text text = new Text(table, SWT.NONE | extraStyle);
330 text.setText(selectedItem.getText(column));
331 org.eclipse.swt.widgets.Listener listener =
332 new org.eclipse.swt.widgets.Listener() {
334 public void handleEvent(Event e) {
335 if(e.type == SWT.Traverse) {
336 if (e.detail == SWT.TRAVERSE_ESCAPE) {
341 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
342 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
343 || e.detail == SWT.TRAVERSE_MNEMONIC)
346 final String newValue = text.getText();
347 if(e.type == SWT.Traverse) {
352 if (validator != null) {
353 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
358 if (writer != null) {
359 Simantics.getSession().async(new WriteRequest() {
361 public void perform(WriteGraph graph) throws DatabaseException {
362 writer.perform(graph, newValue);
370 editor.setEditor(text, selectedItem, column);
371 text.addListener(SWT.FocusOut, listener);
372 text.addListener(SWT.Traverse, listener);
374 if (validator != null) {
375 org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
377 public void handleEvent(Event e) {
378 final String newValue = text.getText();
379 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
381 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
382 text.setToolTipText(error);
385 text.setBackground(null);
386 text.setToolTipText(null);
390 text.addListener(SWT.Modify, validationListener);
394 private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
396 String rangeStr = (lowInclusive ? "[" : "(") + minStr + ".." + maxStr + (highInclusive ? "]" : ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
397 return Range.valueOf(rangeStr);
398 } catch (IllegalArgumentException e) {
399 // Limits are invalid
400 throw new RangeException(e.getMessage(), e);
404 private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
405 Combo rng = new Combo(parent, SWT.READ_ONLY);
406 rng.add(Messages.ComponentTypeViewerData_Inclusive);
407 rng.add(Messages.ComponentTypeViewerData_Exclusive);
408 rng.select(inclusive ? 0 : 1);
412 protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
413 // Disallow range editing when the property is not numeric
414 if (propertyInfo.numberType == null)
417 int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
419 // Parse initial range value
421 String rangeStr = selectedItem.getText(column);
423 range = Range.valueOf(rangeStr);
424 } catch (RangeException ex) {
425 range = new Range(Limit.nolimit(), Limit.nolimit());
428 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
429 GridLayoutFactory.fillDefaults().applyTo(shell);
431 Composite composite = new Composite(shell, SWT.NONE);
432 GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
433 GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
435 Label low = new Label(composite, SWT.NONE);
436 low.setText(Messages.ComponentTypeViewerData_MinimumValue);
437 final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
438 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
439 final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
440 Label high = new Label(composite, SWT.NONE);
441 high.setText(Messages.ComponentTypeViewerData_MaximumValue);
442 final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
443 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
444 final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
446 Composite buttonComposite = new Composite(shell, SWT.NONE);
447 GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
448 GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
450 Button ok = new Button(buttonComposite, SWT.NONE);
451 ok.setText(IDialogConstants.OK_LABEL);
452 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
453 Button cancel = new Button(buttonComposite, SWT.NONE);
454 cancel.setText(IDialogConstants.CANCEL_LABEL);
455 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
457 if (range.getLower().getValue() != null)
458 lowText.setText(range.getLower().getValue().toString());
459 if (range.getUpper().getValue() != null)
460 highText.setText(range.getUpper().getValue().toString());
462 shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
464 public void handleEvent(Event event) {
469 ok.addSelectionListener(new SelectionAdapter() {
470 public void widgetSelected(SelectionEvent e) {
472 final Range newRange = parseRange(propertyInfo.numberType,
473 lowText.getText().trim(),
474 highText.getText().trim(),
475 lowSelector.getSelectionIndex() == 0 ? true : false,
476 highSelector.getSelectionIndex() == 0 ? true : false);
480 if (propertyInfo.immutable)
483 Simantics.getSession().async(new WriteRequest() {
485 public void perform(WriteGraph graph)
486 throws DatabaseException {
487 graph.markUndoPoint();
488 ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
491 } catch (RangeException ex) {
492 ErrorLogger.defaultLogError(ex);
496 cancel.addSelectionListener(new SelectionAdapter() {
497 public void widgetSelected(SelectionEvent e) {
503 Point size = shell.getSize();
505 Display display = table.getDisplay();
506 Rectangle clientArea = display.getClientArea();
507 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
508 Rectangle b = selectedItemBounds;
513 if ((b.x + b.width) > clientArea.width)
514 b.x -= b.x + b.width - clientArea.width;
515 if (b.height > clientArea.height)
516 b.height = clientArea.height;
517 if ((b.y + b.height) > clientArea.height)
518 b.y -= b.y + b.height - clientArea.height;
520 shell.setBounds(selectedItemBounds);
524 public void editMultilineText(Table table, TableEditor editor,
525 final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
526 Rectangle selectedItemBounds, int column, final StringWriter writer)
528 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
529 GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
530 final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
531 GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
532 text.setText(selectedItem.getText(column));
533 org.eclipse.swt.widgets.Listener listener =
534 new org.eclipse.swt.widgets.Listener() {
536 public void handleEvent(Event e) {
537 final String newValue = text.getText();
539 if (e.type == SWT.Traverse) {
540 if (e.detail == SWT.TRAVERSE_ESCAPE) {
545 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
546 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
547 || e.detail == SWT.TRAVERSE_MNEMONIC)
549 if ((e.stateMask & SWT.CTRL) == 0)
556 if (propertyInfo.immutable)
559 if (writer != null) {
560 Simantics.getSession().async(new WriteRequest() {
562 public void perform(WriteGraph graph) throws DatabaseException {
563 writer.perform(graph, newValue);
570 String helpText = propertyInfo.immutable ? Messages.ComponentTypeViewerData_ESCToClose : Messages.ComponentTypeViewerData_CtrlEnterApplyChanges;
571 Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
572 GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
573 help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) ); //$NON-NLS-1$
575 Display display = table.getDisplay();
576 Rectangle clientArea = display.getClientArea();
577 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
578 Rectangle b = selectedItemBounds;
582 if ((b.x + b.width) > clientArea.width)
583 b.x -= b.x + b.width - clientArea.width;
584 if (b.height > clientArea.height)
585 b.height = clientArea.height;
586 if ((b.y + b.height) > clientArea.height)
587 b.y -= b.y + b.height - clientArea.height;
589 shell.setBounds(selectedItemBounds);
595 text.addListener(SWT.Traverse, listener);
596 shell.addListener(SWT.Deactivate, listener);
599 private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
600 if (propertyName.equals(propertyInfo.name))
602 for (ComponentTypeViewerPropertyInfo info : properties) {
603 if (propertyName.equals(info.name))
604 return NLS.bind(Messages.ComponentTypeViewerData_PropertyNameInUse, propertyName);
606 for (NamedResource cp : connectionPoints) {
607 if (propertyName.equals(cp.getName()))
608 return NLS.bind(Messages.ComponentTypeViewerData_NameInUse, propertyName );
610 Matcher m = namePattern.matcher(propertyName);
612 return NLS.bind(Messages.ComponentTypeViewerData_ContainsInvalidCharacters, propertyName, namePattern.pattern());