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.IMessageProvider;
12 import org.eclipse.jface.layout.GridDataFactory;
13 import org.eclipse.jface.layout.GridLayoutFactory;
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.threads.ThreadUtils;
51 import org.simantics.utils.ui.ErrorLogger;
53 public class ComponentTypeViewerData {
55 * Used to validate property names.
57 public static final Pattern PROPERTY_NAME_PATTERN =
58 Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*");
60 public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
81 public Resource componentType;
82 public FormToolkit tk;
84 public UnitLibrary unitLibrary = UnitLibrary.createDefault();
85 public boolean readOnly;
86 public NamedResource[] connectionPoints;
87 public ComponentTypeViewerPropertyInfo[] properties;
89 public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
91 this.componentType = componentType;
95 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
96 Pattern namePattern) {
97 editName(table, editor, propertyInfo, selectedItem, column, namePattern, null);
100 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
101 Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
102 editName(table, editor, propertyInfo, selectedItem, column,
104 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
108 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
109 Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter, Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
110 editName(table, editor, propertyInfo, selectedItem, column, nameFilter,
111 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
115 public void editName(Table table, TableEditor editor, ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
116 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator)
118 editName(table, editor, propertyInfo, selectedItem, column, nameValidator, null);
121 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
122 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator, DbConsumer<WriteGraph> extraWriter) {
123 editName(table, editor, propertyInfo, selectedItem, column, null, nameValidator, extraWriter);
126 public void editName(
129 final ComponentTypeViewerPropertyInfo propertyInfo,
130 TableItem selectedItem,
132 Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter,
133 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator,
134 DbConsumer<WriteGraph> extraWriter) {
135 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
136 final Text text = new Text(table, SWT.NONE | extraStyle);
137 org.eclipse.swt.widgets.Listener listener =
138 new org.eclipse.swt.widgets.Listener() {
140 public void handleEvent(Event e) {
141 if (e.type == SWT.Dispose) {
142 form.setMessage(null);
144 } else if (e.type == SWT.Verify) {
145 // Filter input if necessary
146 e.text = nameFilter != null ? nameFilter.apply(propertyInfo, e.text) : e.text;
148 } else if (e.type == SWT.Modify) {
149 // validate current name
150 String error = nameValidator.apply(propertyInfo, text.getText());
152 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
153 form.setMessage(error, IMessageProvider.ERROR);
155 text.setBackground(null);
156 form.setMessage(null);
159 } else if (e.type == SWT.Traverse) {
160 if (e.detail == SWT.TRAVERSE_ESCAPE) {
165 if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
169 final String newValue = text.getText();
172 String error = nameValidator.apply(propertyInfo, newValue);
176 if (propertyInfo.immutable)
179 Simantics.getSession().async(new WriteRequest() {
181 public void perform(WriteGraph graph)
182 throws DatabaseException {
183 graph.markUndoPoint();
184 Layer0 L0 = Layer0.getInstance(graph);
185 String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
186 String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";
187 String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
188 boolean setLabel = oldLabel == null
189 || oldLabel.isEmpty()
190 || oldCamelCasedLabel.isEmpty()
191 || oldCamelCasedLabel.equals(oldLabel);
193 ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
195 ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
197 if (extraWriter != null)
198 extraWriter.accept(graph);
203 if (nameFilter != null)
204 text.addListener(SWT.Verify, listener);
205 text.addListener(SWT.Modify, listener);
206 text.addListener(SWT.Deactivate, listener);
207 text.addListener(SWT.Traverse, listener);
208 text.addListener(SWT.Dispose, listener);
210 text.setText(selectedItem.getText(column));
214 editor.setEditor(text, selectedItem, column);
217 public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {
218 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
219 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
220 combo.setText(selectedItem.getText(column));
221 for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
222 combo.add(suggestion);
223 org.eclipse.swt.widgets.Listener listener =
224 new org.eclipse.swt.widgets.Listener() {
226 public void handleEvent(Event e) {
227 if(e.type == SWT.Traverse) {
228 if (e.detail == SWT.TRAVERSE_ESCAPE) {
233 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
234 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
235 || e.detail == SWT.TRAVERSE_MNEMONIC)
238 final String newValue = combo.getText();
239 if (e.type == SWT.Traverse) {
244 if (propertyInfo.immutable)
247 Simantics.getSession().async(new WriteRequest() {
249 public void perform(WriteGraph graph)
250 throws DatabaseException {
251 graph.markUndoPoint();
252 ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
258 editor.setEditor(combo, selectedItem, column);
259 combo.addListener(SWT.FocusOut, listener);
260 combo.addListener(SWT.Traverse, listener);
263 public void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
264 // Disallow unit editing for non-numeric configuration properties
265 if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
268 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
269 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
270 String initialValue = selectedItem.getText(column);
271 List<String> units = new ArrayList<>( unitLibrary.getUnits() );
272 Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
275 for (String unit : units) {
277 if (unit.equals(initialValue))
281 combo.setText(initialValue);
283 org.eclipse.swt.widgets.Listener listener =
284 new org.eclipse.swt.widgets.Listener() {
286 public void handleEvent(Event e) {
287 if(e.type == SWT.Traverse) {
288 if (e.detail == SWT.TRAVERSE_ESCAPE) {
293 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
294 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
295 || e.detail == SWT.TRAVERSE_MNEMONIC)
298 final String newValue = combo.getText();
299 if(e.type == SWT.Traverse) {
304 if (propertyInfo.immutable)
307 Simantics.getSession().async(new WriteRequest() {
309 public void perform(WriteGraph graph)
310 throws DatabaseException {
311 graph.markUndoPoint();
312 ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
318 editor.setEditor(combo, selectedItem, column);
319 combo.addListener(SWT.Deactivate, listener);
320 combo.addListener(SWT.Traverse, listener);
323 public void editValue(Table table, TableEditor editor,
324 final ComponentTypeViewerPropertyInfo propertyInfo,
325 TableItem selectedItem, int column,
326 final StringWriter writer,
327 final Function4<RequestProcessor, Resource, Resource, String, String> validator)
329 int extraStyle = writer == null ? SWT.READ_ONLY : 0;
330 final Text text = new Text(table, SWT.NONE | extraStyle);
331 text.setText(selectedItem.getText(column));
332 org.eclipse.swt.widgets.Listener listener =
333 new org.eclipse.swt.widgets.Listener() {
335 public void handleEvent(Event e) {
336 if(e.type == SWT.Traverse) {
337 if (e.detail == SWT.TRAVERSE_ESCAPE) {
342 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
343 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
344 || e.detail == SWT.TRAVERSE_MNEMONIC)
347 final String newValue = text.getText();
348 if(e.type == SWT.Traverse) {
353 if (validator != null) {
354 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
359 if (writer != null) {
360 Simantics.getSession().async(new WriteRequest() {
362 public void perform(WriteGraph graph) throws DatabaseException {
363 writer.perform(graph, newValue);
371 editor.setEditor(text, selectedItem, column);
372 text.addListener(SWT.FocusOut, listener);
373 text.addListener(SWT.Traverse, listener);
375 if (validator != null) {
376 org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
378 private ScheduledFuture<?> future;
381 public void handleEvent(Event e) {
382 final String newValue = text.getText();
383 if (future != null && !future.isCancelled())
385 future = ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
386 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
387 if (!text.isDisposed()) {
388 text.getDisplay().asyncExec(() -> {
389 if (!text.isDisposed()) {
391 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
392 text.setToolTipText(error);
395 text.setBackground(null);
396 text.setToolTipText(null);
402 }, 500, TimeUnit.MILLISECONDS);
405 text.addListener(SWT.Modify, validationListener);
409 private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
411 String rangeStr = (lowInclusive ? "[" : "(") + minStr + ".." + maxStr + (highInclusive ? "]" : ")");
412 return Range.valueOf(rangeStr);
413 } catch (IllegalArgumentException e) {
414 // Limits are invalid
415 throw new RangeException(e.getMessage(), e);
419 private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
420 Combo rng = new Combo(parent, SWT.READ_ONLY);
421 rng.add("Inclusive");
422 rng.add("Exclusive");
423 rng.select(inclusive ? 0 : 1);
427 protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
428 // Disallow range editing when the property is not numeric
429 if (propertyInfo.numberType == null)
432 int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
434 // Parse initial range value
436 String rangeStr = selectedItem.getText(column);
438 range = Range.valueOf(rangeStr);
439 } catch (RangeException ex) {
440 range = new Range(Limit.nolimit(), Limit.nolimit());
443 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
444 GridLayoutFactory.fillDefaults().applyTo(shell);
446 Composite composite = new Composite(shell, SWT.NONE);
447 GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
448 GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
450 Label low = new Label(composite, SWT.NONE);
451 low.setText("Minimum Value:");
452 final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
453 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
454 final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
455 Label high = new Label(composite, SWT.NONE);
456 high.setText("Maximum Value:");
457 final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
458 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
459 final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
461 Composite buttonComposite = new Composite(shell, SWT.NONE);
462 GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
463 GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
465 Button ok = new Button(buttonComposite, SWT.NONE);
467 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
468 Button cancel = new Button(buttonComposite, SWT.NONE);
469 cancel.setText("&Cancel");
470 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
472 if (range.getLower().getValue() != null)
473 lowText.setText(range.getLower().getValue().toString());
474 if (range.getUpper().getValue() != null)
475 highText.setText(range.getUpper().getValue().toString());
477 shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
479 public void handleEvent(Event event) {
484 ok.addSelectionListener(new SelectionAdapter() {
485 public void widgetSelected(SelectionEvent e) {
487 final Range newRange = parseRange(propertyInfo.numberType,
488 lowText.getText().trim(),
489 highText.getText().trim(),
490 lowSelector.getSelectionIndex() == 0 ? true : false,
491 highSelector.getSelectionIndex() == 0 ? true : false);
495 if (propertyInfo.immutable)
498 Simantics.getSession().async(new WriteRequest() {
500 public void perform(WriteGraph graph)
501 throws DatabaseException {
502 graph.markUndoPoint();
503 ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
506 } catch (RangeException ex) {
507 ErrorLogger.defaultLogError(ex);
511 cancel.addSelectionListener(new SelectionAdapter() {
512 public void widgetSelected(SelectionEvent e) {
518 Point size = shell.getSize();
520 Display display = table.getDisplay();
521 Rectangle clientArea = display.getClientArea();
522 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
523 Rectangle b = selectedItemBounds;
528 if ((b.x + b.width) > clientArea.width)
529 b.x -= b.x + b.width - clientArea.width;
530 if (b.height > clientArea.height)
531 b.height = clientArea.height;
532 if ((b.y + b.height) > clientArea.height)
533 b.y -= b.y + b.height - clientArea.height;
535 shell.setBounds(selectedItemBounds);
539 public void editMultilineText(Table table, TableEditor editor,
540 final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
541 Rectangle selectedItemBounds, int column, final StringWriter writer)
543 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
544 GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
545 final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
546 GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
547 text.setText(selectedItem.getText(column));
548 org.eclipse.swt.widgets.Listener listener =
549 new org.eclipse.swt.widgets.Listener() {
551 public void handleEvent(Event e) {
552 final String newValue = text.getText();
554 if (e.type == SWT.Traverse) {
555 if (e.detail == SWT.TRAVERSE_ESCAPE) {
560 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
561 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
562 || e.detail == SWT.TRAVERSE_MNEMONIC)
564 if ((e.stateMask & SWT.CTRL) == 0)
571 if (propertyInfo.immutable)
574 if (writer != null) {
575 Simantics.getSession().async(new WriteRequest() {
577 public void perform(WriteGraph graph) throws DatabaseException {
578 writer.perform(graph, newValue);
585 String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";
586 Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
587 GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
588 help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );
590 Display display = table.getDisplay();
591 Rectangle clientArea = display.getClientArea();
592 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
593 Rectangle b = selectedItemBounds;
597 if ((b.x + b.width) > clientArea.width)
598 b.x -= b.x + b.width - clientArea.width;
599 if (b.height > clientArea.height)
600 b.height = clientArea.height;
601 if ((b.y + b.height) > clientArea.height)
602 b.y -= b.y + b.height - clientArea.height;
604 shell.setBounds(selectedItemBounds);
610 text.addListener(SWT.Traverse, listener);
611 shell.addListener(SWT.Deactivate, listener);
614 private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
615 if (propertyName.equals(propertyInfo.name))
617 for (ComponentTypeViewerPropertyInfo info : properties) {
618 if (propertyName.equals(info.name))
619 return "Property name '" + propertyName + "' is already in use.";
621 for (NamedResource cp : connectionPoints) {
622 if (propertyName.equals(cp.getName()))
623 return "Name '" + propertyName + "' is already used for a terminal.";
625 Matcher m = namePattern.matcher(propertyName);
627 return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "
628 + namePattern.pattern() + ".";