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.layer0.Layer0;
44 import org.simantics.modeling.userComponent.ComponentTypeCommands;
45 import org.simantics.scl.runtime.function.Function2;
46 import org.simantics.scl.runtime.function.Function4;
47 import org.simantics.utils.ui.ErrorLogger;
49 public class ComponentTypeViewerData {
51 * Used to validate property names.
53 public static final Pattern PROPERTY_NAME_PATTERN =
54 Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*");
56 public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
77 public Resource componentType;
78 public FormToolkit tk;
80 public UnitLibrary unitLibrary = UnitLibrary.createDefault();
81 public boolean readOnly;
82 public NamedResource[] connectionPoints;
83 public ComponentTypeViewerPropertyInfo[] properties;
85 public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
87 this.componentType = componentType;
91 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
92 Pattern namePattern) {
93 editName(table, editor, propertyInfo, selectedItem, column,
94 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern));
97 public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
98 Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator) {
99 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
100 final Text text = new Text(table, SWT.NONE | extraStyle);
101 org.eclipse.swt.widgets.Listener listener =
102 new org.eclipse.swt.widgets.Listener() {
104 public void handleEvent(Event e) {
105 if (e.type == SWT.Dispose) {
106 form.setMessage(null);
110 if (e.type == SWT.Modify) {
111 // validate current name
112 String error = nameValidator.apply(propertyInfo, text.getText());
114 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
115 form.setMessage(error, IMessageProvider.ERROR);
117 text.setBackground(null);
118 form.setMessage(null);
123 if (e.type == SWT.Traverse) {
124 if (e.detail == SWT.TRAVERSE_ESCAPE) {
129 if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
133 final String newValue = text.getText();
136 String error = nameValidator.apply(propertyInfo, newValue);
140 if (propertyInfo.immutable)
143 Simantics.getSession().async(new WriteRequest() {
145 public void perform(WriteGraph graph)
146 throws DatabaseException {
147 graph.markUndoPoint();
148 Layer0 L0 = Layer0.getInstance(graph);
149 String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
150 String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";
151 String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
152 boolean setLabel = oldLabel == null
153 || oldLabel.isEmpty()
154 || oldCamelCasedLabel.isEmpty()
155 || oldCamelCasedLabel.equals(oldLabel);
157 ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
159 ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
164 text.addListener(SWT.Modify, listener);
165 text.addListener(SWT.Deactivate, listener);
166 text.addListener(SWT.Traverse, listener);
167 text.addListener(SWT.Dispose, listener);
169 text.setText(selectedItem.getText(column));
173 editor.setEditor(text, selectedItem, column);
176 public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {
177 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
178 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
179 combo.setText(selectedItem.getText(column));
180 for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
181 combo.add(suggestion);
182 org.eclipse.swt.widgets.Listener listener =
183 new org.eclipse.swt.widgets.Listener() {
185 public void handleEvent(Event e) {
186 if(e.type == SWT.Traverse) {
187 if (e.detail == SWT.TRAVERSE_ESCAPE) {
192 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
193 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
194 || e.detail == SWT.TRAVERSE_MNEMONIC)
197 final String newValue = combo.getText();
198 if (e.type == SWT.Traverse) {
203 if (propertyInfo.immutable)
206 Simantics.getSession().async(new WriteRequest() {
208 public void perform(WriteGraph graph)
209 throws DatabaseException {
210 graph.markUndoPoint();
211 ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
217 editor.setEditor(combo, selectedItem, column);
218 combo.addListener(SWT.FocusOut, listener);
219 combo.addListener(SWT.Traverse, listener);
222 protected void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
223 // Disallow unit editing for non-numeric configuration properties
224 if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
227 int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
228 final Combo combo = new Combo(table, SWT.NONE | extraStyle);
229 String initialValue = selectedItem.getText(column);
230 List<String> units = new ArrayList<>( unitLibrary.getUnits() );
231 Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
234 for (String unit : units) {
236 if (unit.equals(initialValue))
240 combo.setText(initialValue);
242 org.eclipse.swt.widgets.Listener listener =
243 new org.eclipse.swt.widgets.Listener() {
245 public void handleEvent(Event e) {
246 if(e.type == SWT.Traverse) {
247 if (e.detail == SWT.TRAVERSE_ESCAPE) {
252 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
253 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
254 || e.detail == SWT.TRAVERSE_MNEMONIC)
257 final String newValue = combo.getText();
258 if(e.type == SWT.Traverse) {
263 if (propertyInfo.immutable)
266 Simantics.getSession().async(new WriteRequest() {
268 public void perform(WriteGraph graph)
269 throws DatabaseException {
270 graph.markUndoPoint();
271 ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
277 editor.setEditor(combo, selectedItem, column);
278 combo.addListener(SWT.Deactivate, listener);
279 combo.addListener(SWT.Traverse, listener);
282 public void editValue(Table table, TableEditor editor,
283 final ComponentTypeViewerPropertyInfo propertyInfo,
284 TableItem selectedItem, int column,
285 final StringWriter writer,
286 final Function4<RequestProcessor, Resource, Resource, String, String> validator)
288 int extraStyle = writer == null ? SWT.READ_ONLY : 0;
289 final Text text = new Text(table, SWT.NONE | extraStyle);
290 text.setText(selectedItem.getText(column));
291 org.eclipse.swt.widgets.Listener listener =
292 new org.eclipse.swt.widgets.Listener() {
294 public void handleEvent(Event e) {
295 if(e.type == SWT.Traverse) {
296 if (e.detail == SWT.TRAVERSE_ESCAPE) {
301 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
302 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
303 || e.detail == SWT.TRAVERSE_MNEMONIC)
306 final String newValue = text.getText();
307 if(e.type == SWT.Traverse) {
312 if (validator != null) {
313 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
318 if (writer != null) {
319 Simantics.getSession().async(new WriteRequest() {
321 public void perform(WriteGraph graph) throws DatabaseException {
322 writer.perform(graph, newValue);
330 editor.setEditor(text, selectedItem, column);
331 text.addListener(SWT.FocusOut, listener);
332 text.addListener(SWT.Traverse, listener);
334 if (validator != null) {
335 org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
337 public void handleEvent(Event e) {
338 final String newValue = text.getText();
339 String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
341 text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
342 text.setToolTipText(error);
345 text.setBackground(null);
346 text.setToolTipText(null);
350 text.addListener(SWT.Modify, validationListener);
354 private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
356 String rangeStr = (lowInclusive ? "[" : "(") + minStr + ".." + maxStr + (highInclusive ? "]" : ")");
357 return Range.valueOf(rangeStr);
358 } catch (IllegalArgumentException e) {
359 // Limits are invalid
360 throw new RangeException(e.getMessage(), e);
364 private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
365 Combo rng = new Combo(parent, SWT.READ_ONLY);
366 rng.add("Inclusive");
367 rng.add("Exclusive");
368 rng.select(inclusive ? 0 : 1);
372 protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
373 // Disallow range editing when the property is not numeric
374 if (propertyInfo.numberType == null)
377 int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
379 // Parse initial range value
381 String rangeStr = selectedItem.getText(column);
383 range = Range.valueOf(rangeStr);
384 } catch (RangeException ex) {
385 range = new Range(Limit.nolimit(), Limit.nolimit());
388 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
389 GridLayoutFactory.fillDefaults().applyTo(shell);
391 Composite composite = new Composite(shell, SWT.NONE);
392 GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
393 GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
395 Label low = new Label(composite, SWT.NONE);
396 low.setText("Minimum Value:");
397 final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
398 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
399 final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
400 Label high = new Label(composite, SWT.NONE);
401 high.setText("Maximum Value:");
402 final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
403 GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
404 final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
406 Composite buttonComposite = new Composite(shell, SWT.NONE);
407 GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
408 GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
410 Button ok = new Button(buttonComposite, SWT.NONE);
412 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
413 Button cancel = new Button(buttonComposite, SWT.NONE);
414 cancel.setText("&Cancel");
415 GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
417 if (range.getLower().getValue() != null)
418 lowText.setText(range.getLower().getValue().toString());
419 if (range.getUpper().getValue() != null)
420 highText.setText(range.getUpper().getValue().toString());
422 shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
424 public void handleEvent(Event event) {
429 ok.addSelectionListener(new SelectionAdapter() {
430 public void widgetSelected(SelectionEvent e) {
432 final Range newRange = parseRange(propertyInfo.numberType,
433 lowText.getText().trim(),
434 highText.getText().trim(),
435 lowSelector.getSelectionIndex() == 0 ? true : false,
436 highSelector.getSelectionIndex() == 0 ? true : false);
440 if (propertyInfo.immutable)
443 Simantics.getSession().async(new WriteRequest() {
445 public void perform(WriteGraph graph)
446 throws DatabaseException {
447 graph.markUndoPoint();
448 ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
451 } catch (RangeException ex) {
452 ErrorLogger.defaultLogError(ex);
456 cancel.addSelectionListener(new SelectionAdapter() {
457 public void widgetSelected(SelectionEvent e) {
463 Point size = shell.getSize();
465 Display display = table.getDisplay();
466 Rectangle clientArea = display.getClientArea();
467 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
468 Rectangle b = selectedItemBounds;
473 if ((b.x + b.width) > clientArea.width)
474 b.x -= b.x + b.width - clientArea.width;
475 if (b.height > clientArea.height)
476 b.height = clientArea.height;
477 if ((b.y + b.height) > clientArea.height)
478 b.y -= b.y + b.height - clientArea.height;
480 shell.setBounds(selectedItemBounds);
484 protected void editMultilineText(Table table, TableEditor editor,
485 final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
486 Rectangle selectedItemBounds, int column, final StringWriter writer)
488 final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
489 GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
490 final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
491 GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
492 text.setText(selectedItem.getText(column));
493 org.eclipse.swt.widgets.Listener listener =
494 new org.eclipse.swt.widgets.Listener() {
496 public void handleEvent(Event e) {
497 final String newValue = text.getText();
499 if (e.type == SWT.Traverse) {
500 if (e.detail == SWT.TRAVERSE_ESCAPE) {
505 if (e.detail == SWT.TRAVERSE_ARROW_NEXT
506 || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
507 || e.detail == SWT.TRAVERSE_MNEMONIC)
509 if ((e.stateMask & SWT.CTRL) == 0)
516 if (propertyInfo.immutable)
519 if (writer != null) {
520 Simantics.getSession().async(new WriteRequest() {
522 public void perform(WriteGraph graph) throws DatabaseException {
523 writer.perform(graph, newValue);
530 String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";
531 Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
532 GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
533 help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );
535 Display display = table.getDisplay();
536 Rectangle clientArea = display.getClientArea();
537 Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
538 Rectangle b = selectedItemBounds;
542 if ((b.x + b.width) > clientArea.width)
543 b.x -= b.x + b.width - clientArea.width;
544 if (b.height > clientArea.height)
545 b.height = clientArea.height;
546 if ((b.y + b.height) > clientArea.height)
547 b.y -= b.y + b.height - clientArea.height;
549 shell.setBounds(selectedItemBounds);
555 text.addListener(SWT.Traverse, listener);
556 shell.addListener(SWT.Deactivate, listener);
559 private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
560 if (propertyName.equals(propertyInfo.name))
562 for (ComponentTypeViewerPropertyInfo info : properties) {
563 if (propertyName.equals(info.name))
564 return "Property name '" + propertyName + "' is already in use.";
566 for (NamedResource cp : connectionPoints) {
567 if (propertyName.equals(cp.getName()))
568 return "Name '" + propertyName + "' is already used for a terminal.";
570 Matcher m = namePattern.matcher(propertyName);
572 return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "
573 + namePattern.pattern() + ".";