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.IMessageProvider;
10 import org.eclipse.jface.layout.GridDataFactory;
11 import org.eclipse.jface.layout.GridLayoutFactory;
12 import org.eclipse.swt.SWT;
13 import org.eclipse.swt.custom.StyledText;
14 import org.eclipse.swt.custom.TableEditor;
15 import org.eclipse.swt.events.SelectionAdapter;
16 import org.eclipse.swt.events.SelectionEvent;
17 import org.eclipse.swt.graphics.Point;
18 import org.eclipse.swt.graphics.Rectangle;
19 import org.eclipse.swt.widgets.Button;
20 import org.eclipse.swt.widgets.Combo;
21 import org.eclipse.swt.widgets.Composite;
22 import org.eclipse.swt.widgets.Display;
23 import org.eclipse.swt.widgets.Event;
24 import org.eclipse.swt.widgets.Label;
25 import org.eclipse.swt.widgets.Shell;
26 import org.eclipse.swt.widgets.Table;
27 import org.eclipse.swt.widgets.TableItem;
28 import org.eclipse.swt.widgets.Text;
29 import org.eclipse.ui.forms.widgets.Form;
30 import org.eclipse.ui.forms.widgets.FormToolkit;
31 import org.simantics.Simantics;
32 import org.simantics.databoard.type.NumberType;
33 import org.simantics.databoard.units.internal.library.UnitLibrary;
34 import org.simantics.databoard.util.Limit;
35 import org.simantics.databoard.util.Range;
36 import org.simantics.databoard.util.RangeException;
37 import org.simantics.db.RequestProcessor;
38 import org.simantics.db.Resource;
39 import org.simantics.db.WriteGraph;
40 import org.simantics.db.common.NamedResource;
41 import org.simantics.db.common.request.WriteRequest;
42 import org.simantics.db.exception.DatabaseException;
43 import org.simantics.db.function.DbConsumer;
44 import org.simantics.layer0.Layer0;
45 import org.simantics.modeling.userComponent.ComponentTypeCommands;
46 import org.simantics.scl.runtime.function.Function2;
47 import org.simantics.scl.runtime.function.Function4;
48 import org.simantics.utils.ui.ErrorLogger;
50 public class ComponentTypeViewerData {
52 * Used to validate property names.
54 public static final Pattern PROPERTY_NAME_PATTERN =
55 Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*");
57 public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
78 public Resource componentType;
79 public FormToolkit tk;
81 public UnitLibrary unitLibrary = UnitLibrary.createDefault();
82 public boolean readOnly;
83 public NamedResource[] connectionPoints;
84 public ComponentTypeViewerPropertyInfo[] properties;
86 public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
88 this.componentType = componentType;
92 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
93 Pattern namePattern) {
94 editName(table, editor, propertyInfo, selectedItem, column, namePattern, null);
97 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
98 Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
99 editName(table, editor, propertyInfo, selectedItem, column,
101 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
105 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
106 Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter, Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
107 editName(table, editor, propertyInfo, selectedItem, column, nameFilter,
108 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
112 public void editName(Table table, TableEditor editor, ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
113 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator)
115 editName(table, editor, propertyInfo, selectedItem, column, nameValidator, null);
118 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
119 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator, DbConsumer<WriteGraph> extraWriter) {
120 editName(table, editor, propertyInfo, selectedItem, column, null, nameValidator, extraWriter);
123 public void editName(
126 final ComponentTypeViewerPropertyInfo propertyInfo,
127 TableItem selectedItem,
129 Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter,
130 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator,
131 DbConsumer<WriteGraph> extraWriter) {
132 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
133 final Text text = new Text(table, SWT.NONE | extraStyle);
134 org.eclipse.swt.widgets.Listener listener =
135 new org.eclipse.swt.widgets.Listener() {
137 public void handleEvent(Event e) {
138 if (e.type == SWT.Dispose) {
139 form.setMessage(null);
141 } else if (e.type == SWT.Verify) {
142 // Filter input if necessary
143 e.text = nameFilter != null ? nameFilter.apply(propertyInfo, e.text) : e.text;
145 } else if (e.type == SWT.Modify) {
146 // validate current name
147 String error = nameValidator.apply(propertyInfo, text.getText());
149 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
150 form.setMessage(error, IMessageProvider.ERROR);
152 text.setBackground(null);
153 form.setMessage(null);
156 } else if (e.type == SWT.Traverse) {
157 if (e.detail == SWT.TRAVERSE_ESCAPE) {
162 if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
166 final String newValue = text.getText();
169 String error = nameValidator.apply(propertyInfo, newValue);
173 if (propertyInfo.immutable)
176 Simantics.getSession().async(new WriteRequest() {
178 public void perform(WriteGraph graph)
179 throws DatabaseException {
180 graph.markUndoPoint();
181 Layer0 L0 = Layer0.getInstance(graph);
182 String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
183 String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";
184 String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
185 boolean setLabel = oldLabel == null
186 || oldLabel.isEmpty()
187 || oldCamelCasedLabel.isEmpty()
188 || oldCamelCasedLabel.equals(oldLabel);
190 ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
192 ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
194 if (extraWriter != null)
195 extraWriter.accept(graph);
200 if (nameFilter != null)
201 text.addListener(SWT.Verify, listener);
202 text.addListener(SWT.Modify, listener);
203 text.addListener(SWT.Deactivate, listener);
204 text.addListener(SWT.Traverse, listener);
205 text.addListener(SWT.Dispose, listener);
207 text.setText(selectedItem.getText(column));
211 editor.setEditor(text, selectedItem, column);
214 public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {
215 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
216 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
217 combo.setText(selectedItem.getText(column));
218 for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
219 combo.add(suggestion);
220 org.eclipse.swt.widgets.Listener listener =
221 new org.eclipse.swt.widgets.Listener() {
223 public void handleEvent(Event e) {
224 if(e.type == SWT.Traverse) {
225 if (e.detail == SWT.TRAVERSE_ESCAPE) {
230 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
231 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
232 || e.detail == SWT.TRAVERSE_MNEMONIC)
235 final String newValue = combo.getText();
236 if (e.type == SWT.Traverse) {
241 if (propertyInfo.immutable)
244 Simantics.getSession().async(new WriteRequest() {
246 public void perform(WriteGraph graph)
247 throws DatabaseException {
248 graph.markUndoPoint();
249 ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
255 editor.setEditor(combo, selectedItem, column);
256 combo.addListener(SWT.FocusOut, listener);
257 combo.addListener(SWT.Traverse, listener);
260 public void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
261 // Disallow unit editing for non-numeric configuration properties
262 if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
265 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
266 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
267 String initialValue = selectedItem.getText(column);
268 List<String> units = new ArrayList<>( unitLibrary.getUnits() );
269 Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
272 for (String unit : units) {
274 if (unit.equals(initialValue))
278 combo.setText(initialValue);
280 org.eclipse.swt.widgets.Listener listener =
281 new org.eclipse.swt.widgets.Listener() {
283 public void handleEvent(Event e) {
284 if(e.type == SWT.Traverse) {
285 if (e.detail == SWT.TRAVERSE_ESCAPE) {
290 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
291 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
292 || e.detail == SWT.TRAVERSE_MNEMONIC)
295 final String newValue = combo.getText();
296 if(e.type == SWT.Traverse) {
301 if (propertyInfo.immutable)
304 Simantics.getSession().async(new WriteRequest() {
306 public void perform(WriteGraph graph)
307 throws DatabaseException {
308 graph.markUndoPoint();
309 ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
315 editor.setEditor(combo, selectedItem, column);
316 combo.addListener(SWT.Deactivate, listener);
317 combo.addListener(SWT.Traverse, listener);
320 public void editValue(Table table, TableEditor editor,
321 final ComponentTypeViewerPropertyInfo propertyInfo,
322 TableItem selectedItem, int column,
323 final StringWriter writer,
324 final Function4<RequestProcessor, Resource, Resource, String, String> validator)
326 int extraStyle = writer == null ? SWT.READ_ONLY : 0;
327 final Text text = new Text(table, SWT.NONE | extraStyle);
328 text.setText(selectedItem.getText(column));
329 org.eclipse.swt.widgets.Listener listener =
330 new org.eclipse.swt.widgets.Listener() {
332 public void handleEvent(Event e) {
333 if(e.type == SWT.Traverse) {
334 if (e.detail == SWT.TRAVERSE_ESCAPE) {
339 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
340 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
341 || e.detail == SWT.TRAVERSE_MNEMONIC)
344 final String newValue = text.getText();
345 if(e.type == SWT.Traverse) {
350 if (validator != null) {
351 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
356 if (writer != null) {
357 Simantics.getSession().async(new WriteRequest() {
359 public void perform(WriteGraph graph) throws DatabaseException {
360 writer.perform(graph, newValue);
368 editor.setEditor(text, selectedItem, column);
369 text.addListener(SWT.FocusOut, listener);
370 text.addListener(SWT.Traverse, listener);
372 if (validator != null) {
373 org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
375 public void handleEvent(Event e) {
376 final String newValue = text.getText();
377 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
379 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
380 text.setToolTipText(error);
383 text.setBackground(null);
384 text.setToolTipText(null);
388 text.addListener(SWT.Modify, validationListener);
392 private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
394 String rangeStr = (lowInclusive ? "[" : "(") + minStr + ".." + maxStr + (highInclusive ? "]" : ")");
395 return Range.valueOf(rangeStr);
396 } catch (IllegalArgumentException e) {
397 // Limits are invalid
398 throw new RangeException(e.getMessage(), e);
402 private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
403 Combo rng = new Combo(parent, SWT.READ_ONLY);
404 rng.add("Inclusive");
405 rng.add("Exclusive");
406 rng.select(inclusive ? 0 : 1);
410 protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
411 // Disallow range editing when the property is not numeric
412 if (propertyInfo.numberType == null)
415 int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
417 // Parse initial range value
419 String rangeStr = selectedItem.getText(column);
421 range = Range.valueOf(rangeStr);
422 } catch (RangeException ex) {
423 range = new Range(Limit.nolimit(), Limit.nolimit());
426 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
427 GridLayoutFactory.fillDefaults().applyTo(shell);
429 Composite composite = new Composite(shell, SWT.NONE);
430 GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
431 GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
433 Label low = new Label(composite, SWT.NONE);
434 low.setText("Minimum Value:");
435 final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
436 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
437 final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
438 Label high = new Label(composite, SWT.NONE);
439 high.setText("Maximum Value:");
440 final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
441 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
442 final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
444 Composite buttonComposite = new Composite(shell, SWT.NONE);
445 GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
446 GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
448 Button ok = new Button(buttonComposite, SWT.NONE);
450 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
451 Button cancel = new Button(buttonComposite, SWT.NONE);
452 cancel.setText("&Cancel");
453 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
455 if (range.getLower().getValue() != null)
456 lowText.setText(range.getLower().getValue().toString());
457 if (range.getUpper().getValue() != null)
458 highText.setText(range.getUpper().getValue().toString());
460 shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
462 public void handleEvent(Event event) {
467 ok.addSelectionListener(new SelectionAdapter() {
468 public void widgetSelected(SelectionEvent e) {
470 final Range newRange = parseRange(propertyInfo.numberType,
471 lowText.getText().trim(),
472 highText.getText().trim(),
473 lowSelector.getSelectionIndex() == 0 ? true : false,
474 highSelector.getSelectionIndex() == 0 ? true : false);
478 if (propertyInfo.immutable)
481 Simantics.getSession().async(new WriteRequest() {
483 public void perform(WriteGraph graph)
484 throws DatabaseException {
485 graph.markUndoPoint();
486 ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
489 } catch (RangeException ex) {
490 ErrorLogger.defaultLogError(ex);
494 cancel.addSelectionListener(new SelectionAdapter() {
495 public void widgetSelected(SelectionEvent e) {
501 Point size = shell.getSize();
503 Display display = table.getDisplay();
504 Rectangle clientArea = display.getClientArea();
505 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
506 Rectangle b = selectedItemBounds;
511 if ((b.x + b.width) > clientArea.width)
512 b.x -= b.x + b.width - clientArea.width;
513 if (b.height > clientArea.height)
514 b.height = clientArea.height;
515 if ((b.y + b.height) > clientArea.height)
516 b.y -= b.y + b.height - clientArea.height;
518 shell.setBounds(selectedItemBounds);
522 public void editMultilineText(Table table, TableEditor editor,
523 final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
524 Rectangle selectedItemBounds, int column, final StringWriter writer)
526 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
527 GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
528 final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
529 GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
530 text.setText(selectedItem.getText(column));
531 org.eclipse.swt.widgets.Listener listener =
532 new org.eclipse.swt.widgets.Listener() {
534 public void handleEvent(Event e) {
535 final String newValue = text.getText();
537 if (e.type == SWT.Traverse) {
538 if (e.detail == SWT.TRAVERSE_ESCAPE) {
543 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
544 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
545 || e.detail == SWT.TRAVERSE_MNEMONIC)
547 if ((e.stateMask & SWT.CTRL) == 0)
554 if (propertyInfo.immutable)
557 if (writer != null) {
558 Simantics.getSession().async(new WriteRequest() {
560 public void perform(WriteGraph graph) throws DatabaseException {
561 writer.perform(graph, newValue);
568 String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";
569 Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
570 GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
571 help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );
573 Display display = table.getDisplay();
574 Rectangle clientArea = display.getClientArea();
575 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
576 Rectangle b = selectedItemBounds;
580 if ((b.x + b.width) > clientArea.width)
581 b.x -= b.x + b.width - clientArea.width;
582 if (b.height > clientArea.height)
583 b.height = clientArea.height;
584 if ((b.y + b.height) > clientArea.height)
585 b.y -= b.y + b.height - clientArea.height;
587 shell.setBounds(selectedItemBounds);
593 text.addListener(SWT.Traverse, listener);
594 shell.addListener(SWT.Deactivate, listener);
597 private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
598 if (propertyName.equals(propertyInfo.name))
600 for (ComponentTypeViewerPropertyInfo info : properties) {
601 if (propertyName.equals(info.name))
602 return "Property name '" + propertyName + "' is already in use.";
604 for (NamedResource cp : connectionPoints) {
605 if (propertyName.equals(cp.getName()))
606 return "Name '" + propertyName + "' is already used for a terminal.";
608 Matcher m = namePattern.matcher(propertyName);
610 return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "
611 + namePattern.pattern() + ".";