1 package org.simantics.modeling.ui.componentTypeEditor;
3 import java.util.ArrayList;
4 import java.util.Collections;
6 import java.util.concurrent.ScheduledFuture;
7 import java.util.concurrent.TimeUnit;
8 import java.util.regex.Matcher;
9 import java.util.regex.Pattern;
11 import org.eclipse.jface.dialogs.IDialogConstants;
12 import org.eclipse.jface.dialogs.IMessageProvider;
13 import org.eclipse.jface.layout.GridDataFactory;
14 import org.eclipse.jface.layout.GridLayoutFactory;
15 import org.eclipse.osgi.util.NLS;
16 import org.eclipse.swt.SWT;
17 import org.eclipse.swt.custom.StyledText;
18 import org.eclipse.swt.custom.TableEditor;
19 import org.eclipse.swt.events.SelectionAdapter;
20 import org.eclipse.swt.events.SelectionEvent;
21 import org.eclipse.swt.graphics.Point;
22 import org.eclipse.swt.graphics.Rectangle;
23 import org.eclipse.swt.widgets.Button;
24 import org.eclipse.swt.widgets.Combo;
25 import org.eclipse.swt.widgets.Composite;
26 import org.eclipse.swt.widgets.Display;
27 import org.eclipse.swt.widgets.Event;
28 import org.eclipse.swt.widgets.Label;
29 import org.eclipse.swt.widgets.Shell;
30 import org.eclipse.swt.widgets.Table;
31 import org.eclipse.swt.widgets.TableItem;
32 import org.eclipse.swt.widgets.Text;
33 import org.eclipse.ui.forms.widgets.Form;
34 import org.eclipse.ui.forms.widgets.FormToolkit;
35 import org.simantics.Simantics;
36 import org.simantics.databoard.type.NumberType;
37 import org.simantics.databoard.units.internal.library.UnitLibrary;
38 import org.simantics.databoard.util.Limit;
39 import org.simantics.databoard.util.Range;
40 import org.simantics.databoard.util.RangeException;
41 import org.simantics.db.RequestProcessor;
42 import org.simantics.db.Resource;
43 import org.simantics.db.WriteGraph;
44 import org.simantics.db.common.NamedResource;
45 import org.simantics.db.common.request.WriteRequest;
46 import org.simantics.db.exception.DatabaseException;
47 import org.simantics.db.function.DbConsumer;
48 import org.simantics.layer0.Layer0;
49 import org.simantics.modeling.userComponent.ComponentTypeCommands;
50 import org.simantics.scl.runtime.function.Function2;
51 import org.simantics.scl.runtime.function.Function4;
52 import org.simantics.utils.threads.ThreadUtils;
53 import org.simantics.utils.ui.ErrorLogger;
55 public class ComponentTypeViewerData {
57 * Used to validate property names.
59 public static final Pattern PROPERTY_NAME_PATTERN =
60 Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*"); //$NON-NLS-1$
62 public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
63 "Double", //$NON-NLS-1$
64 "Integer", //$NON-NLS-1$
65 "Float", //$NON-NLS-1$
66 "String", //$NON-NLS-1$
67 "Boolean", //$NON-NLS-1$
69 "[Double]", //$NON-NLS-1$
70 "[Integer]", //$NON-NLS-1$
71 "[Float]", //$NON-NLS-1$
72 "[String]", //$NON-NLS-1$
73 "[Boolean]", //$NON-NLS-1$
74 "[Long]", //$NON-NLS-1$
75 "Vector Double", //$NON-NLS-1$
76 "Vector Integer", //$NON-NLS-1$
77 "Vector Float", //$NON-NLS-1$
78 "Vector String", //$NON-NLS-1$
79 "Vector Boolean", //$NON-NLS-1$
80 "Vector Long" //$NON-NLS-1$
83 public Resource componentType;
84 public FormToolkit tk;
86 public UnitLibrary unitLibrary = UnitLibrary.createDefault();
87 public boolean readOnly;
88 public NamedResource[] connectionPoints;
89 public ComponentTypeViewerPropertyInfo[] properties;
91 public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
93 this.componentType = componentType;
97 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
98 Pattern namePattern) {
99 editName(table, editor, propertyInfo, selectedItem, column, namePattern, null);
102 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
103 Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
104 editName(table, editor, propertyInfo, selectedItem, column,
106 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
110 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
111 Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter, Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
112 editName(table, editor, propertyInfo, selectedItem, column, nameFilter,
113 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
117 public void editName(Table table, TableEditor editor, ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
118 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator)
120 editName(table, editor, propertyInfo, selectedItem, column, nameValidator, null);
123 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
124 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator, DbConsumer<WriteGraph> extraWriter) {
125 editName(table, editor, propertyInfo, selectedItem, column, null, nameValidator, extraWriter);
128 public void editName(
131 final ComponentTypeViewerPropertyInfo propertyInfo,
132 TableItem selectedItem,
134 Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter,
135 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator,
136 DbConsumer<WriteGraph> extraWriter) {
137 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
138 final Text text = new Text(table, SWT.NONE | extraStyle);
139 org.eclipse.swt.widgets.Listener listener =
140 new org.eclipse.swt.widgets.Listener() {
142 public void handleEvent(Event e) {
143 if (e.type == SWT.Dispose) {
144 form.setMessage(null);
146 } else if (e.type == SWT.Verify) {
147 // Filter input if necessary
148 e.text = nameFilter != null ? nameFilter.apply(propertyInfo, e.text) : e.text;
150 } else if (e.type == SWT.Modify) {
151 // validate current name
152 String error = nameValidator.apply(propertyInfo, text.getText());
154 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
155 form.setMessage(error, IMessageProvider.ERROR);
157 text.setBackground(null);
158 form.setMessage(null);
161 } else if (e.type == SWT.Traverse) {
162 if (e.detail == SWT.TRAVERSE_ESCAPE) {
167 if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
171 final String newValue = text.getText();
174 String error = nameValidator.apply(propertyInfo, newValue);
178 if (propertyInfo.immutable)
181 Simantics.getSession().async(new WriteRequest() {
183 public void perform(WriteGraph graph)
184 throws DatabaseException {
185 graph.markUndoPoint();
186 Layer0 L0 = Layer0.getInstance(graph);
187 String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
188 String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : ""; //$NON-NLS-1$
189 String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
190 boolean setLabel = oldLabel == null
191 || oldLabel.isEmpty()
192 || oldCamelCasedLabel.isEmpty()
193 || oldCamelCasedLabel.equals(oldLabel);
195 ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
197 ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
199 if (extraWriter != null)
200 extraWriter.accept(graph);
205 if (nameFilter != null)
206 text.addListener(SWT.Verify, listener);
207 text.addListener(SWT.Modify, listener);
208 text.addListener(SWT.Deactivate, listener);
209 text.addListener(SWT.Traverse, listener);
210 text.addListener(SWT.Dispose, listener);
212 text.setText(selectedItem.getText(column));
216 editor.setEditor(text, selectedItem, column);
219 public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, String range, final boolean convertDefaultValue) {
220 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
221 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
222 combo.setText(selectedItem.getText(column));
223 for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
224 combo.add(suggestion);
225 org.eclipse.swt.widgets.Listener listener =
226 new org.eclipse.swt.widgets.Listener() {
228 public void handleEvent(Event e) {
229 if(e.type == SWT.Traverse) {
230 if (e.detail == SWT.TRAVERSE_ESCAPE) {
235 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
236 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
237 || e.detail == SWT.TRAVERSE_MNEMONIC)
240 final String newValue = combo.getText();
241 if (e.type == SWT.Traverse) {
246 if (propertyInfo.immutable)
249 Simantics.getSession().async(new WriteRequest() {
251 public void perform(WriteGraph graph)
252 throws DatabaseException {
253 graph.markUndoPoint();
254 ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
255 if (range != null) ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, range);
261 editor.setEditor(combo, selectedItem, column);
262 combo.addListener(SWT.FocusOut, listener);
263 combo.addListener(SWT.Traverse, listener);
266 public void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
267 // Disallow unit editing for non-numeric configuration properties
268 if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
271 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
272 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
273 String initialValue = selectedItem.getText(column);
274 List<String> units = new ArrayList<>( unitLibrary.getUnits() );
275 Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
278 for (String unit : units) {
280 if (unit.equals(initialValue))
284 combo.setText(initialValue);
286 org.eclipse.swt.widgets.Listener listener =
287 new org.eclipse.swt.widgets.Listener() {
289 public void handleEvent(Event e) {
290 if(e.type == SWT.Traverse) {
291 if (e.detail == SWT.TRAVERSE_ESCAPE) {
296 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
297 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
298 || e.detail == SWT.TRAVERSE_MNEMONIC)
301 final String newValue = combo.getText();
302 if(e.type == SWT.Traverse) {
307 if (propertyInfo.immutable)
310 Simantics.getSession().async(new WriteRequest() {
312 public void perform(WriteGraph graph)
313 throws DatabaseException {
314 graph.markUndoPoint();
315 ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
321 editor.setEditor(combo, selectedItem, column);
322 combo.addListener(SWT.Deactivate, listener);
323 combo.addListener(SWT.Traverse, listener);
326 public void editValue(Table table, TableEditor editor,
327 final ComponentTypeViewerPropertyInfo propertyInfo,
328 TableItem selectedItem, int column,
329 final StringWriter writer,
330 final Function4<RequestProcessor, Resource, Resource, String, String> validator)
332 int extraStyle = writer == null ? SWT.READ_ONLY : 0;
333 final Text text = new Text(table, SWT.NONE | extraStyle);
334 text.setText(selectedItem.getText(column));
335 org.eclipse.swt.widgets.Listener listener =
336 new org.eclipse.swt.widgets.Listener() {
338 public void handleEvent(Event e) {
339 if(e.type == SWT.Traverse) {
340 if (e.detail == SWT.TRAVERSE_ESCAPE) {
345 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
346 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
347 || e.detail == SWT.TRAVERSE_MNEMONIC)
350 final String newValue = text.getText();
351 if(e.type == SWT.Traverse) {
356 if (validator != null) {
357 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
362 if (writer != null) {
363 Simantics.getSession().async(new WriteRequest() {
365 public void perform(WriteGraph graph) throws DatabaseException {
366 writer.perform(graph, newValue);
374 editor.setEditor(text, selectedItem, column);
375 text.addListener(SWT.FocusOut, listener);
376 text.addListener(SWT.Traverse, listener);
378 if (validator != null) {
379 org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
381 private ScheduledFuture<?> future;
384 public void handleEvent(Event e) {
385 final String newValue = text.getText();
386 if (future != null && !future.isCancelled())
388 future = ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
389 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
390 if (!text.isDisposed()) {
391 text.getDisplay().asyncExec(() -> {
392 if (!text.isDisposed()) {
394 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
395 text.setToolTipText(error);
398 text.setBackground(null);
399 text.setToolTipText(null);
405 }, 500, TimeUnit.MILLISECONDS);
408 text.addListener(SWT.Modify, validationListener);
412 private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
414 String rangeStr = (lowInclusive ? "[" : "(") + minStr + ".." + maxStr + (highInclusive ? "]" : ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
415 return Range.valueOf(rangeStr);
416 } catch (IllegalArgumentException e) {
417 // Limits are invalid
418 throw new RangeException(e.getMessage(), e);
422 private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
423 Combo rng = new Combo(parent, SWT.READ_ONLY);
424 rng.add(Messages.ComponentTypeViewerData_Inclusive);
425 rng.add(Messages.ComponentTypeViewerData_Exclusive);
426 rng.select(inclusive ? 0 : 1);
430 protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
431 // Disallow range editing when the property is not numeric
432 if (propertyInfo.numberType == null)
435 int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
437 // Parse initial range value
439 String rangeStr = selectedItem.getText(column);
441 range = Range.valueOf(rangeStr);
442 } catch (RangeException ex) {
443 range = new Range(Limit.nolimit(), Limit.nolimit());
446 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
447 GridLayoutFactory.fillDefaults().applyTo(shell);
449 Composite composite = new Composite(shell, SWT.NONE);
450 GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
451 GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
453 Label low = new Label(composite, SWT.NONE);
454 low.setText(Messages.ComponentTypeViewerData_MinimumValue);
455 final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
456 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
457 final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
458 Label high = new Label(composite, SWT.NONE);
459 high.setText(Messages.ComponentTypeViewerData_MaximumValue);
460 final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
461 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
462 final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
464 Composite buttonComposite = new Composite(shell, SWT.NONE);
465 GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
466 GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
468 Button ok = new Button(buttonComposite, SWT.NONE);
469 ok.setText(IDialogConstants.OK_LABEL);
470 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
471 Button cancel = new Button(buttonComposite, SWT.NONE);
472 cancel.setText(IDialogConstants.CANCEL_LABEL);
473 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
475 if (range.getLower().getValue() != null)
476 lowText.setText(range.getLower().getValue().toString());
477 if (range.getUpper().getValue() != null)
478 highText.setText(range.getUpper().getValue().toString());
480 shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
482 public void handleEvent(Event event) {
487 ok.addSelectionListener(new SelectionAdapter() {
488 public void widgetSelected(SelectionEvent e) {
490 final Range newRange = parseRange(propertyInfo.numberType,
491 lowText.getText().trim(),
492 highText.getText().trim(),
493 lowSelector.getSelectionIndex() == 0 ? true : false,
494 highSelector.getSelectionIndex() == 0 ? true : false);
498 if (propertyInfo.immutable)
501 Simantics.getSession().async(new WriteRequest() {
503 public void perform(WriteGraph graph)
504 throws DatabaseException {
505 graph.markUndoPoint();
506 ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
509 } catch (RangeException ex) {
510 ErrorLogger.defaultLogError(ex);
514 cancel.addSelectionListener(new SelectionAdapter() {
515 public void widgetSelected(SelectionEvent e) {
521 Point size = shell.getSize();
523 Display display = table.getDisplay();
524 Rectangle clientArea = display.getClientArea();
525 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
526 Rectangle b = selectedItemBounds;
531 if ((b.x + b.width) > clientArea.width)
532 b.x -= b.x + b.width - clientArea.width;
533 if (b.height > clientArea.height)
534 b.height = clientArea.height;
535 if ((b.y + b.height) > clientArea.height)
536 b.y -= b.y + b.height - clientArea.height;
538 shell.setBounds(selectedItemBounds);
542 public void editMultilineText(Table table, TableEditor editor,
543 final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
544 Rectangle selectedItemBounds, int column, final StringWriter writer)
546 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
547 GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
548 final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
549 GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
550 text.setText(selectedItem.getText(column));
551 org.eclipse.swt.widgets.Listener listener =
552 new org.eclipse.swt.widgets.Listener() {
554 public void handleEvent(Event e) {
555 final String newValue = text.getText();
557 if (e.type == SWT.Traverse) {
558 if (e.detail == SWT.TRAVERSE_ESCAPE) {
563 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
564 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
565 || e.detail == SWT.TRAVERSE_MNEMONIC)
567 if ((e.stateMask & SWT.CTRL) == 0)
574 if (propertyInfo.immutable)
577 if (writer != null) {
578 Simantics.getSession().async(new WriteRequest() {
580 public void perform(WriteGraph graph) throws DatabaseException {
581 writer.perform(graph, newValue);
588 String helpText = propertyInfo.immutable ? Messages.ComponentTypeViewerData_ESCToClose : Messages.ComponentTypeViewerData_CtrlEnterApplyChanges;
589 Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
590 GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
591 help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) ); //$NON-NLS-1$
593 Display display = table.getDisplay();
594 Rectangle clientArea = display.getClientArea();
595 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
596 Rectangle b = selectedItemBounds;
600 if ((b.x + b.width) > clientArea.width)
601 b.x -= b.x + b.width - clientArea.width;
602 if (b.height > clientArea.height)
603 b.height = clientArea.height;
604 if ((b.y + b.height) > clientArea.height)
605 b.y -= b.y + b.height - clientArea.height;
607 shell.setBounds(selectedItemBounds);
613 text.addListener(SWT.Traverse, listener);
614 shell.addListener(SWT.Deactivate, listener);
617 private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
618 if (propertyName.equals(propertyInfo.name))
620 for (ComponentTypeViewerPropertyInfo info : properties) {
621 if (propertyName.equals(info.name))
622 return NLS.bind(Messages.ComponentTypeViewerData_PropertyNameInUse, propertyName);
624 for (NamedResource cp : connectionPoints) {
625 if (propertyName.equals(cp.getName()))
626 return NLS.bind(Messages.ComponentTypeViewerData_NameInUse, propertyName );
628 Matcher m = namePattern.matcher(propertyName);
630 return NLS.bind(Messages.ComponentTypeViewerData_ContainsInvalidCharacters, propertyName, namePattern.pattern());