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,
100 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
104 public void editName(Table table, TableEditor editor, ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
105 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator)
107 editName(table, editor, propertyInfo, selectedItem, column, nameValidator, null);
110 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
111 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator, DbConsumer<WriteGraph> extraWriter) {
112 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
113 final Text text = new Text(table, SWT.NONE | extraStyle);
114 org.eclipse.swt.widgets.Listener listener =
115 new org.eclipse.swt.widgets.Listener() {
117 public void handleEvent(Event e) {
118 if (e.type == SWT.Dispose) {
119 form.setMessage(null);
123 if (e.type == SWT.Modify) {
124 // validate current name
125 String error = nameValidator.apply(propertyInfo, text.getText());
127 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
128 form.setMessage(error, IMessageProvider.ERROR);
130 text.setBackground(null);
131 form.setMessage(null);
136 if (e.type == SWT.Traverse) {
137 if (e.detail == SWT.TRAVERSE_ESCAPE) {
142 if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
146 final String newValue = text.getText();
149 String error = nameValidator.apply(propertyInfo, newValue);
153 if (propertyInfo.immutable)
156 Simantics.getSession().async(new WriteRequest() {
158 public void perform(WriteGraph graph)
159 throws DatabaseException {
160 graph.markUndoPoint();
161 Layer0 L0 = Layer0.getInstance(graph);
162 String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
163 String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";
164 String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
165 boolean setLabel = oldLabel == null
166 || oldLabel.isEmpty()
167 || oldCamelCasedLabel.isEmpty()
168 || oldCamelCasedLabel.equals(oldLabel);
170 ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
172 ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
174 if (extraWriter != null)
175 extraWriter.accept(graph);
180 text.addListener(SWT.Modify, listener);
181 text.addListener(SWT.Deactivate, listener);
182 text.addListener(SWT.Traverse, listener);
183 text.addListener(SWT.Dispose, listener);
185 text.setText(selectedItem.getText(column));
189 editor.setEditor(text, selectedItem, column);
192 public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {
193 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
194 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
195 combo.setText(selectedItem.getText(column));
196 for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
197 combo.add(suggestion);
198 org.eclipse.swt.widgets.Listener listener =
199 new org.eclipse.swt.widgets.Listener() {
201 public void handleEvent(Event e) {
202 if(e.type == SWT.Traverse) {
203 if (e.detail == SWT.TRAVERSE_ESCAPE) {
208 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
209 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
210 || e.detail == SWT.TRAVERSE_MNEMONIC)
213 final String newValue = combo.getText();
214 if (e.type == SWT.Traverse) {
219 if (propertyInfo.immutable)
222 Simantics.getSession().async(new WriteRequest() {
224 public void perform(WriteGraph graph)
225 throws DatabaseException {
226 graph.markUndoPoint();
227 ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
233 editor.setEditor(combo, selectedItem, column);
234 combo.addListener(SWT.FocusOut, listener);
235 combo.addListener(SWT.Traverse, listener);
238 public void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
239 // Disallow unit editing for non-numeric configuration properties
240 if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
243 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
244 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
245 String initialValue = selectedItem.getText(column);
246 List<String> units = new ArrayList<>( unitLibrary.getUnits() );
247 Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
250 for (String unit : units) {
252 if (unit.equals(initialValue))
256 combo.setText(initialValue);
258 org.eclipse.swt.widgets.Listener listener =
259 new org.eclipse.swt.widgets.Listener() {
261 public void handleEvent(Event e) {
262 if(e.type == SWT.Traverse) {
263 if (e.detail == SWT.TRAVERSE_ESCAPE) {
268 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
269 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
270 || e.detail == SWT.TRAVERSE_MNEMONIC)
273 final String newValue = combo.getText();
274 if(e.type == SWT.Traverse) {
279 if (propertyInfo.immutable)
282 Simantics.getSession().async(new WriteRequest() {
284 public void perform(WriteGraph graph)
285 throws DatabaseException {
286 graph.markUndoPoint();
287 ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
293 editor.setEditor(combo, selectedItem, column);
294 combo.addListener(SWT.Deactivate, listener);
295 combo.addListener(SWT.Traverse, listener);
298 public void editValue(Table table, TableEditor editor,
299 final ComponentTypeViewerPropertyInfo propertyInfo,
300 TableItem selectedItem, int column,
301 final StringWriter writer,
302 final Function4<RequestProcessor, Resource, Resource, String, String> validator)
304 int extraStyle = writer == null ? SWT.READ_ONLY : 0;
305 final Text text = new Text(table, SWT.NONE | extraStyle);
306 text.setText(selectedItem.getText(column));
307 org.eclipse.swt.widgets.Listener listener =
308 new org.eclipse.swt.widgets.Listener() {
310 public void handleEvent(Event e) {
311 if(e.type == SWT.Traverse) {
312 if (e.detail == SWT.TRAVERSE_ESCAPE) {
317 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
318 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
319 || e.detail == SWT.TRAVERSE_MNEMONIC)
322 final String newValue = text.getText();
323 if(e.type == SWT.Traverse) {
328 if (validator != null) {
329 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
334 if (writer != null) {
335 Simantics.getSession().async(new WriteRequest() {
337 public void perform(WriteGraph graph) throws DatabaseException {
338 writer.perform(graph, newValue);
346 editor.setEditor(text, selectedItem, column);
347 text.addListener(SWT.FocusOut, listener);
348 text.addListener(SWT.Traverse, listener);
350 if (validator != null) {
351 org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
353 public void handleEvent(Event e) {
354 final String newValue = text.getText();
355 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
357 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
358 text.setToolTipText(error);
361 text.setBackground(null);
362 text.setToolTipText(null);
366 text.addListener(SWT.Modify, validationListener);
370 private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
372 String rangeStr = (lowInclusive ? "[" : "(") + minStr + ".." + maxStr + (highInclusive ? "]" : ")");
373 return Range.valueOf(rangeStr);
374 } catch (IllegalArgumentException e) {
375 // Limits are invalid
376 throw new RangeException(e.getMessage(), e);
380 private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
381 Combo rng = new Combo(parent, SWT.READ_ONLY);
382 rng.add("Inclusive");
383 rng.add("Exclusive");
384 rng.select(inclusive ? 0 : 1);
388 protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
389 // Disallow range editing when the property is not numeric
390 if (propertyInfo.numberType == null)
393 int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
395 // Parse initial range value
397 String rangeStr = selectedItem.getText(column);
399 range = Range.valueOf(rangeStr);
400 } catch (RangeException ex) {
401 range = new Range(Limit.nolimit(), Limit.nolimit());
404 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
405 GridLayoutFactory.fillDefaults().applyTo(shell);
407 Composite composite = new Composite(shell, SWT.NONE);
408 GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
409 GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
411 Label low = new Label(composite, SWT.NONE);
412 low.setText("Minimum Value:");
413 final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
414 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
415 final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
416 Label high = new Label(composite, SWT.NONE);
417 high.setText("Maximum Value:");
418 final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
419 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
420 final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
422 Composite buttonComposite = new Composite(shell, SWT.NONE);
423 GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
424 GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
426 Button ok = new Button(buttonComposite, SWT.NONE);
428 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
429 Button cancel = new Button(buttonComposite, SWT.NONE);
430 cancel.setText("&Cancel");
431 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
433 if (range.getLower().getValue() != null)
434 lowText.setText(range.getLower().getValue().toString());
435 if (range.getUpper().getValue() != null)
436 highText.setText(range.getUpper().getValue().toString());
438 shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
440 public void handleEvent(Event event) {
445 ok.addSelectionListener(new SelectionAdapter() {
446 public void widgetSelected(SelectionEvent e) {
448 final Range newRange = parseRange(propertyInfo.numberType,
449 lowText.getText().trim(),
450 highText.getText().trim(),
451 lowSelector.getSelectionIndex() == 0 ? true : false,
452 highSelector.getSelectionIndex() == 0 ? true : false);
456 if (propertyInfo.immutable)
459 Simantics.getSession().async(new WriteRequest() {
461 public void perform(WriteGraph graph)
462 throws DatabaseException {
463 graph.markUndoPoint();
464 ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
467 } catch (RangeException ex) {
468 ErrorLogger.defaultLogError(ex);
472 cancel.addSelectionListener(new SelectionAdapter() {
473 public void widgetSelected(SelectionEvent e) {
479 Point size = shell.getSize();
481 Display display = table.getDisplay();
482 Rectangle clientArea = display.getClientArea();
483 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
484 Rectangle b = selectedItemBounds;
489 if ((b.x + b.width) > clientArea.width)
490 b.x -= b.x + b.width - clientArea.width;
491 if (b.height > clientArea.height)
492 b.height = clientArea.height;
493 if ((b.y + b.height) > clientArea.height)
494 b.y -= b.y + b.height - clientArea.height;
496 shell.setBounds(selectedItemBounds);
500 protected void editMultilineText(Table table, TableEditor editor,
501 final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
502 Rectangle selectedItemBounds, int column, final StringWriter writer)
504 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
505 GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
506 final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
507 GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
508 text.setText(selectedItem.getText(column));
509 org.eclipse.swt.widgets.Listener listener =
510 new org.eclipse.swt.widgets.Listener() {
512 public void handleEvent(Event e) {
513 final String newValue = text.getText();
515 if (e.type == SWT.Traverse) {
516 if (e.detail == SWT.TRAVERSE_ESCAPE) {
521 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
522 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
523 || e.detail == SWT.TRAVERSE_MNEMONIC)
525 if ((e.stateMask & SWT.CTRL) == 0)
532 if (propertyInfo.immutable)
535 if (writer != null) {
536 Simantics.getSession().async(new WriteRequest() {
538 public void perform(WriteGraph graph) throws DatabaseException {
539 writer.perform(graph, newValue);
546 String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";
547 Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
548 GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
549 help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );
551 Display display = table.getDisplay();
552 Rectangle clientArea = display.getClientArea();
553 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
554 Rectangle b = selectedItemBounds;
558 if ((b.x + b.width) > clientArea.width)
559 b.x -= b.x + b.width - clientArea.width;
560 if (b.height > clientArea.height)
561 b.height = clientArea.height;
562 if ((b.y + b.height) > clientArea.height)
563 b.y -= b.y + b.height - clientArea.height;
565 shell.setBounds(selectedItemBounds);
571 text.addListener(SWT.Traverse, listener);
572 shell.addListener(SWT.Deactivate, listener);
575 private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
576 if (propertyName.equals(propertyInfo.name))
578 for (ComponentTypeViewerPropertyInfo info : properties) {
579 if (propertyName.equals(info.name))
580 return "Property name '" + propertyName + "' is already in use.";
582 for (NamedResource cp : connectionPoints) {
583 if (propertyName.equals(cp.getName()))
584 return "Name '" + propertyName + "' is already used for a terminal.";
586 Matcher m = namePattern.matcher(propertyName);
588 return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "
589 + namePattern.pattern() + ".";