package org.simantics.plant3d.dialog; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Set; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceLocator; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ListViewer; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.ExpandBar; import org.eclipse.swt.widgets.ExpandItem; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.simantics.Simantics; import org.simantics.db.Session; import org.simantics.db.exception.DatabaseException; import org.simantics.plant3d.Activator; import org.simantics.plant3d.ontology.Plant3D; import org.simantics.plant3d.scenegraph.EndComponent; import org.simantics.plant3d.scenegraph.InlineComponent; import org.simantics.plant3d.scenegraph.Nozzle; import org.simantics.plant3d.scenegraph.PipeRun; import org.simantics.plant3d.scenegraph.PipelineComponent; import org.simantics.plant3d.scenegraph.TurnComponent; import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.PositionType; import org.simantics.plant3d.utils.Item; import org.simantics.plant3d.utils.Item.Type; import org.simantics.plant3d.utils.P3DUtil; import org.simantics.utils.ui.ExceptionUtils; public class ComponentSelectionDialog extends Dialog implements ISelectionChangedListener { private static final String DIALOG = "ComponentSelectionDialog"; //$NON-NLS-1$ private IDialogSettings dialogSettings; private double lengthFactor = 1.0; private ResourceManager resourceManager; private String libUri; private Item selected; private Set allowed; private Set filterAllowed; // private boolean positionAdjustment; private PipelineComponent component; private boolean insertAdjustable; private boolean lenghtAdjustable; private PositionType insertPosition = PositionType.NEXT; private Double angle; private Double length; private Double rotationAngle; private String name; private Text nameText; // In-line component private Text lengthText; // Turn component private Text angleText; // Rotated In-line, or turn component. private Text rotationAngleText; // Input for new PipeRun private Double diameter; private Double thickness; private Double turnRadius; private Text diameterText; private Text thicknessText; private Text turnRadiusText; // Validation message label private Label validationLabel; // Position selection private Button startButton; private Button middleButton; private Button endButton; private boolean inlineSplit = false; ListViewer inlineViewer; ListViewer turnViewer; ListViewer endViewer; private Set usedNames; public ComponentSelectionDialog(Shell parentShell, Set allowed, PipelineComponent component) { this(parentShell, allowed, component, Plant3D.URIs.Builtin); } public ComponentSelectionDialog(Shell parentShell, Set allowed, PipelineComponent component, String libUri) { super(parentShell); this.allowed = allowed; this.component = component; filterAllowed = new HashSet(); insertAdjustable = component instanceof InlineComponent ? ((InlineComponent) component).isVariableLength() : false; lenghtAdjustable = false; this.libUri = libUri; usedNames = new HashSet<>(); setShellStyle(getShellStyle() | SWT.RESIZE); IDialogSettings settings = Activator.getDefault().getDialogSettings(); dialogSettings = settings.getSection(DIALOG); if (dialogSettings == null) dialogSettings = settings.addNewSection(DIALOG); if (component.getNext() != null && component.getPrevious() != null) insertPosition = PositionType.PREVIOUS; } private void copyPipeRunParameters() { PipeRun run = component.getPipeRun(); if (component.getNext() == null && component.getAlternativePipeRun() != null) run = component.getAlternativePipeRun(); if (run == null) return; diameter = run.getPipeDiameter(); thickness = run.getPipeThickness(); turnRadius = run.getTurnRadius(); diameterText.setText(Double.toString(diameter * lengthFactor)); thicknessText.setText(Double.toString(thickness * lengthFactor)); turnRadiusText.setText(Double.toString(turnRadius * lengthFactor)); } public void setLengthFactor(double lengthFactor) { this.lengthFactor = lengthFactor; } @Override protected IDialogSettings getDialogBoundsSettings() { return dialogSettings; } @Override protected void configureShell(Shell newShell) { super.configureShell(newShell); newShell.setText("Create pipeline component"); } public void addUsedNames(Collection names) { usedNames.addAll(names); } protected List getItems(Class c, String libUri) throws DatabaseException { Session session = Simantics.getSession(); if (InlineComponent.class.equals(c)) { return P3DUtil.getInlines(session, libUri); } else if (TurnComponent.class.equals(c)) { return P3DUtil.getTurns(session, libUri); } else if (EndComponent.class.equals(c)) { return P3DUtil.getEnds(session, libUri); } else { return null; } } @Override protected Control createDialogArea(Composite parent) { resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent); Composite composite = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(2, false); layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_BOTH)); applyDialogFont(composite); // Grid layout data for fields that grab horizontal space final GridDataFactory horizFillData = GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.TOP); // TODO : we need better classification than inline,turn, and end: // * fixed length inlines // * fixed angle turns // * size changes (requires input for pipe run specs) // * variable length inlines (input for length) // * variable angle turns (input for angle) // * ends List ends = null; List turns = null; List inlines = null; try { ends = getItems(EndComponent.class, libUri); turns = getItems(TurnComponent.class, libUri); inlines = getItems(InlineComponent.class, libUri); } catch (DatabaseException e) { Label label = new Label(composite, SWT.NONE); label.setText("Cannot load pipeline components: " + e.getMessage()); ExceptionUtils.logError(e); return composite; } ends = P3DUtil.filterUserComponents(ends); turns = P3DUtil.filterUserComponents(turns); inlines = P3DUtil.filterUserComponents(inlines); ExpandBar expandBar = new ExpandBar(composite, SWT.V_SCROLL); boolean isOpen = component.getNext() == null || component.getPrevious() == null; if (!inlines.isEmpty()) { if (!isOpen) { // Remove variable length pipe from options ListIterator it = inlines.listIterator(); while (it.hasNext()) { Item item = it.next(); if (item.isVariable()) it.remove(); } } inlineViewer = createItemList(expandBar, inlines, "Inline"); } // Only allow elbows and ends to be added to open ends of the pipelines if (isOpen) { if (!turns.isEmpty()) { turnViewer = createItemList(expandBar, turns, "Elbow"); } if (!ends.isEmpty()) { turnViewer = createItemList(expandBar, ends, "End"); } } GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).span(2, 1).applyTo(expandBar); GridDataFactory.fillDefaults().grab(true, true).minSize(500, 500).hint(500, 500).applyTo(composite); // If there is only one item in the ExpandBar, expand it by default to avoid unnecessary clicks ExpandItem[] expandBarItems = expandBar.getItems(); if (expandBarItems.length == 1) { expandBarItems[0].setExpanded(true); } Label label = new Label(composite, SWT.NONE); label.setText("Position"); Composite buttonComposite = new Composite(composite, SWT.NONE); startButton = new Button(buttonComposite, SWT.TOGGLE); middleButton = new Button(buttonComposite, SWT.TOGGLE); endButton = new Button(buttonComposite, SWT.TOGGLE); startButton.setImage(resourceManager.createImage( ResourceLocator.imageDescriptorFromBundle(Activator.PLUGIN_ID, "icons/insert_start.png").get())); middleButton.setImage(resourceManager.createImage( ResourceLocator.imageDescriptorFromBundle(Activator.PLUGIN_ID, "icons/insert_middle.png").get())); endButton.setImage(resourceManager.createImage( ResourceLocator.imageDescriptorFromBundle(Activator.PLUGIN_ID, "icons/insert_end.png").get())); startButton.setToolTipText("Overlapping insert"); middleButton.setToolTipText("Cutting insert"); endButton.setToolTipText("Adding insert"); horizFillData.applyTo(buttonComposite); GridLayoutFactory.fillDefaults().numColumns(3).applyTo(buttonComposite); startButton.setSelection(insertPosition == PositionType.PREVIOUS); middleButton.setSelection(insertPosition == PositionType.SPLIT); endButton.setSelection(insertPosition == PositionType.NEXT); startButton.setEnabled(false); middleButton.setEnabled(false); endButton.setEnabled(false); startButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateInsertPosition(PositionType.PREVIOUS); } }); middleButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateInsertPosition(PositionType.SPLIT); } }); endButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateInsertPosition(PositionType.NEXT); } }); if (!hasInsertPosition()) { label.setVisible(false); buttonComposite.setVisible(false); } label = new Label(composite, SWT.NONE); label.setText("Name"); nameText = new Text(composite, SWT.SINGLE | SWT.BORDER); label = new Label(composite, SWT.NONE); label.setText("Length"); lengthText = new Text(composite, SWT.SINGLE | SWT.BORDER); label = new Label(composite, SWT.NONE); label.setText("Angle"); angleText = new Text(composite, SWT.SINGLE | SWT.BORDER); label = new Label(composite, SWT.NONE); label.setText("Rotation angle"); rotationAngleText = new Text(composite, SWT.SINGLE | SWT.BORDER); label = new Label(composite, SWT.NONE); label.setText("Diameter"); diameterText = new Text(composite, SWT.SINGLE | SWT.BORDER); label = new Label(composite, SWT.NONE); label.setText("Wall thickness"); thicknessText = new Text(composite, SWT.SINGLE | SWT.BORDER); label = new Label(composite, SWT.NONE); label.setText("Elbow radius"); turnRadiusText = new Text(composite, SWT.SINGLE | SWT.BORDER); validationLabel = new Label(composite, SWT.NONE); validationLabel.setText(""); lengthText.setEnabled(false); angleText.setEnabled(false); rotationAngleText.setEnabled(false); turnRadiusText.setEnabled(false); diameterText.setEnabled(false); thicknessText.setEnabled(false); copyPipeRunParameters(); nameText.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { name = nameText.getText(); validate(); } }); lengthText.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { try { length = Double.parseDouble(lengthText.getText()) / lengthFactor; } catch (NumberFormatException err) { length = null; } validate(); } }); angleText.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { try { angle = Double.parseDouble(angleText.getText()); } catch (NumberFormatException err) { angle = null; } validate(); } }); rotationAngleText.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { try { rotationAngle = Double.parseDouble(rotationAngleText.getText()); } catch (NumberFormatException err) { rotationAngle = null; } validate(); } }); diameterText.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { try { diameter = Double.parseDouble(diameterText.getText()) / lengthFactor; } catch (NumberFormatException err) { diameter = null; } validate(); } }); thicknessText.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { try { thickness = Double.parseDouble(thicknessText.getText()) / lengthFactor; } catch (NumberFormatException err) { thickness = null; } validate(); } }); turnRadiusText.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { try { turnRadius = Double.parseDouble(turnRadiusText.getText()) / lengthFactor; } catch (NumberFormatException err) { turnRadius = null; } validate(); } }); horizFillData.applyTo(nameText); horizFillData.applyTo(lengthText); horizFillData.applyTo(angleText); horizFillData.applyTo(rotationAngleText); horizFillData.applyTo(diameterText); horizFillData.applyTo(thicknessText); horizFillData.applyTo(turnRadiusText); GridDataFactory.fillDefaults().span(2, 1).align(SWT.END, SWT.END).grab(true, false).applyTo(validationLabel); if (!allowed.contains(PositionType.NEXT) && !allowed.contains(PositionType.PREVIOUS)) { turnViewer.getList().setEnabled(false); endViewer.getList().setEnabled(false); inlineSplit = true; } return composite; } private ListViewer createItemList(ExpandBar expandBar, List items, String name) { ExpandItem expandItem = new ExpandItem(expandBar, SWT.NONE); expandItem.setText(name); ListViewer viewer = new ListViewer(expandBar); viewer.setLabelProvider(new ComponentLabelProvider()); viewer.setContentProvider(new ComponentContentProvider()); expandItem.setControl(viewer.getList()); viewer.setInput(items); expandItem.setHeight(viewer.getList().computeSize(SWT.DEFAULT, SWT.DEFAULT).y); viewer.addSelectionChangedListener(this); return viewer; } private boolean hasInsertPosition() { return component.getNext() == null || component.getPrevious() == null; } private void updateInsertPosition(PositionType type) { if (insertPosition == type) return; startButton.setSelection(type == PositionType.PREVIOUS); middleButton.setSelection(type == PositionType.SPLIT); endButton.setSelection(type == PositionType.NEXT); insertPosition = type; } @Override public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection sel = (IStructuredSelection) event.getSelection(); Item i = (Item) sel.getFirstElement(); if (i != null) { selected = i; if (inlineViewer != null && event.getSource() == inlineViewer) { clearSelection(turnViewer); clearSelection(endViewer); } else if (turnViewer != null && event.getSource() == turnViewer) { clearSelection(inlineViewer); clearSelection(endViewer); } else if (endViewer != null && event.getSource() == endViewer) { clearSelection(inlineViewer); clearSelection(turnViewer); } name = generateUniqueName(selected.getName()); nameText.setText(name); validate(); } } private void clearSelection(ListViewer possibleListViewer) { if (possibleListViewer != null) possibleListViewer.setSelection(new StructuredSelection()); } private void validate() { filterAllowed.clear(); Set filterAllowed = new HashSet(); boolean ok = true; String msg = null; if (name.isEmpty() || usedNames.contains(name)) { ok = false; if (msg == null) { if (name.isEmpty()) msg = "Please provide a valid name"; else msg = "Name \"" + name + "\" is already in use"; } } if (selected == null) { ok = false; } else if (selected.isCode()) {// TODO : instead of disabling the button, we should filter the content. ok = false; } else { lenghtAdjustable = ((selected.getType() == Type.INLINE) && (selected.isVariable() || selected.isModifiable())); if (!hasInsertPosition()) { // We are inserting to a fully connected variable length component // only allow insertion within the component startButton.setEnabled(false); middleButton.setEnabled(false); endButton.setEnabled(false); updateInsertPosition(PositionType.PREVIOUS); } else if (insertAdjustable) { switch (selected.getType()) { case END: startButton.setEnabled(false); middleButton.setEnabled(false); endButton.setEnabled(true); updateInsertPosition(PositionType.NEXT); break; case INLINE: if (!selected.isVariable()) { startButton.setEnabled(true); middleButton.setEnabled(true); endButton.setEnabled(true); } else { startButton.setEnabled(false); middleButton.setEnabled(false); endButton.setEnabled(true); updateInsertPosition(PositionType.NEXT); } break; case NOZZLE: startButton.setEnabled(false); middleButton.setEnabled(false); endButton.setEnabled(true); updateInsertPosition(PositionType.NEXT); break; case TURN: startButton.setEnabled(false); middleButton.setEnabled(true); endButton.setEnabled(true); if (insertPosition == PositionType.PREVIOUS) updateInsertPosition(PositionType.NEXT); break; case EQUIPMENT: throw new RuntimeException("Expected component, got equipment " + selected); } } else if (lenghtAdjustable) { if (component instanceof InlineComponent) { startButton.setEnabled(true); middleButton.setEnabled(true); endButton.setEnabled(true); } else if (component instanceof TurnComponent) { startButton.setEnabled(false); middleButton.setEnabled(true); endButton.setEnabled(true); if (insertPosition == PositionType.PREVIOUS) updateInsertPosition(PositionType.NEXT); } else if (component instanceof EndComponent || component instanceof Nozzle) { startButton.setEnabled(false); middleButton.setEnabled(false); endButton.setEnabled(true); updateInsertPosition(PositionType.NEXT); } } else { startButton.setEnabled(false); middleButton.setEnabled(false); endButton.setEnabled(true); } if (selected.isVariable() || selected.isModifiable()) { if (selected.getType() == Type.INLINE) { filterAllowed.add(PositionType.NEXT); filterAllowed.add(PositionType.PREVIOUS); if (inlineSplit && selected.isVariable()) { lengthText.setEnabled(false); angleText.setEnabled(false); rotationAngleText.setEnabled(selected.isRotated()); ok = false; if (msg == null) msg = "Cannot split a straight pipe with a straight pipe"; } else { lengthText.setEnabled(true); angleText.setEnabled(false); rotationAngleText.setEnabled(selected.isRotated()); if (length == null || length <= 0.0) { ok = false; if (msg == null) msg = "Please provide a valid length"; } } } else if (selected.getType() == Type.TURN) { filterAllowed.add(PositionType.NEXT); filterAllowed.add(PositionType.PREVIOUS); lengthText.setEnabled(false); angleText.setEnabled(true); rotationAngleText.setEnabled(true); if (angle == null) { ok = false; if (msg == null) msg = "Please provide a turn angle"; } } else { // this should not happen, since end components should not have variable, or // modifiable flag. lengthText.setEnabled(false); angleText.setEnabled(false); rotationAngleText.setEnabled(false); } } else { lengthText.setEnabled(false); angleText.setEnabled(false); rotationAngleText.setEnabled(selected.getType() == Type.TURN || selected.isRotated()); } if (selected.isSizeChange()) { turnRadiusText.setEnabled(true); diameterText.setEnabled(true); thicknessText.setEnabled(true); if (diameter == null || diameter <= 0.0) { ok = false; if (msg == null) msg = "Please provide a valid diameter"; } if (turnRadius == null || diameter != null && turnRadius < diameter / 2) { ok = false; if (msg == null) msg = "Please provide a valid turn radius"; } if (thickness == null || thickness < 0.0 || diameter != null && thickness >= diameter / 2) { ok = false; if (msg == null) msg = "Please provide a valid wall thickness"; } } else { turnRadiusText.setEnabled(false); diameterText.setEnabled(false); thicknessText.setEnabled(false); } if (!selected.isVariable()) { switch (selected.getType()) { case END: filterAllowed.add(PositionType.NEXT); filterAllowed.add(PositionType.PREVIOUS); break; case NOZZLE: case EQUIPMENT: break; case INLINE: filterAllowed.add(PositionType.NEXT); filterAllowed.add(PositionType.PREVIOUS); filterAllowed.add(PositionType.SPLIT); case TURN: filterAllowed.add(PositionType.NEXT); filterAllowed.add(PositionType.PREVIOUS); } } } for (PositionType t : filterAllowed) { if (allowed.contains(t)) this.filterAllowed.add(t); } validationLabel.setText(msg != null ? msg : ""); validationLabel.requestLayout(); getButton(OK).setEnabled(ok); } private String generateUniqueName(String name) { int i = 1; String newName; while (usedNames.contains((newName = name + "_" + i))) i++; return newName; } public Item getSelected() { return selected; } public String getName() { return name; } public Double getAngle() { return angle; } public Double getLength() { return length; } public Double getRotationAngle() { return rotationAngle; } public Double getDiameter() { return diameter; } public Double getTurnRadius() { return turnRadius; } public Set filterAllowed() { return filterAllowed; } public PositionType getInsertPosition() { return insertPosition; } public boolean isInsertAdjustable() { return insertAdjustable; } public boolean isLenghtAdjustable() { return lenghtAdjustable; } public Double getThickness() { return thickness; } }