SetDimensionsPropertySection.java

/**
 * Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
 * All Rights Reserved.
 *
 * NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
 * OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
 * MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 *
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 * SPDX-License-Identifier: EPL-2.0
 *
 * Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
 *
 * This program includes and/or can make use of certain third party source code, object code, documentation and other
 * files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
 * configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
 * conditions contained in any such Third Party Software or separate license file distributed with such Third Party
 * Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
 * aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
 * censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
 */
package org.osate.ge.aadl2.ui.internal.properties;

import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.IFilter;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.views.properties.tabbed.AbstractPropertySection;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertyConstants;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.ArrayDimension;
import org.osate.aadl2.ArraySize;
import org.osate.aadl2.ArrayableElement;
import org.osate.aadl2.Feature;
import org.osate.aadl2.Property;
import org.osate.aadl2.PropertyConstant;
import org.osate.ge.BusinessObjectSelection;
import org.osate.ge.ProjectUtil;
import org.osate.ge.aadl2.ui.internal.dialogs.EditDimensionsDialog.EditDimensionDialog;
import org.osate.ge.internal.ui.util.InternalPropertySectionUtil;
import org.osate.ge.internal.ui.util.InternalPropertySectionUtil.DragAndDropElement;
import org.osate.ge.internal.ui.util.InternalPropertySectionUtil.DragAndDropSupport;
import org.osate.ge.internal.ui.util.InternalPropertySectionUtil.ExecuteOrderChange;
import org.osate.ge.internal.ui.util.InternalPropertySectionUtil.UpDownButtonSelectionAdapter;
import org.osate.ge.ui.PropertySectionUtil;

public class SetDimensionsPropertySection extends AbstractPropertySection {
	public static class Filter implements IFilter {
		@Override
		public boolean select(final Object toTest) {
			return PropertySectionUtil.isBoCompatible(toTest, bo -> bo instanceof ArrayableElement);
		}
	}

	private final String emptyString = "";
	private BusinessObjectSelection selectedBos;
	private TableViewer tableViewer;
	private Button modifyBtn;
	private Button addBtn;
	private Button deleteBtn;
	private Button upBtn;
	private Button downBtn;
	private int selectedIndex = 0; // Default table index and user selected index

	@Override
	public void createControls(Composite parent, TabbedPropertySheetPage aTabbedPropertySheetPage) {
		super.createControls(parent, aTabbedPropertySheetPage);
		FormData fd;

		final Composite composite = getWidgetFactory().createFlatFormComposite(parent);
		final Composite tableComposite = getWidgetFactory().createComposite(composite);
		fd = new FormData();
		fd.left = new FormAttachment(0, STANDARD_LABEL_WIDTH);
		fd.height = 140;
		fd.width = 250;
		tableComposite.setLayoutData(fd);
		tableViewer = createTableViewer(tableComposite);
		tableViewer.addSelectionChangedListener(event -> {
			final Table table = tableViewer.getTable();
			if (table.getSelectionIndex() >= 0) {
				selectedIndex = table.getSelectionIndex();
			}
			updateMoveButtons(table.getItemCount() - 1);
		});

		// Drag and drop support for changing call sequence
		final DragAndDropSupport dNDSupport = new DragAndDropSupport(tableViewer.getTable(), executeChangeOrder);
		final int operations = dNDSupport.getDragAndDropOperations();
		final Transfer[] types = dNDSupport.getTransferTypes();
		tableViewer.addDropSupport(operations, types, dNDSupport.dropTargetListener);
		tableViewer.addDragSupport(operations, types, dNDSupport.dragSourceListener);

		final TableViewerColumn portCol = InternalPropertySectionUtil.createTableColumnViewer(tableViewer,
				"Array Dimensions", SWT.RESIZE, new CellLabelProvider() {
			@Override
			public void update(final ViewerCell cell) {
				final DragAndDropElement element = (DragAndDropElement) cell.getElement();
				cell.setText("[" + element.getName() + "]");
			}
		});

		portCol.getColumn().setResizable(true);
		tableComposite.setLayout(createTableColumnLayout(portCol.getColumn()));
		modifyBtn = InternalPropertySectionUtil.createButton(getWidgetFactory(), composite, null,
				modifyDimensionSelectionListener, "Modify...", SWT.PUSH);
		final int btnWidth = 60;
		fd = new FormData();
		fd.left = new FormAttachment(tableComposite, ITabbedPropertyConstants.HSPACE);
		fd.top = new FormAttachment(tableComposite, ITabbedPropertyConstants.VMARGIN, SWT.TOP);
		fd.width = btnWidth;
		modifyBtn.setLayoutData(fd);

// Add
		addBtn = InternalPropertySectionUtil.createButton(getWidgetFactory(), composite, SWT.NONE,
				addDimensionSelectionListener, "Add", SWT.PUSH);
		fd = new FormData();
		fd.left = new FormAttachment(tableComposite, ITabbedPropertyConstants.HSPACE);
		fd.top = new FormAttachment(modifyBtn, -ITabbedPropertyConstants.VMARGIN);
		fd.width = btnWidth;
		addBtn.setLayoutData(fd);

// Delete
		deleteBtn = InternalPropertySectionUtil.createButton(getWidgetFactory(), composite, SWT.NONE,
				deleteDimensionSelectionListener, "Delete", SWT.PUSH);
		fd = new FormData();
		fd.left = new FormAttachment(tableComposite, ITabbedPropertyConstants.HSPACE);
		fd.top = new FormAttachment(addBtn, -ITabbedPropertyConstants.VMARGIN);
		fd.width = btnWidth;
		deleteBtn.setLayoutData(fd);

		final UpDownButtonSelectionAdapter moveBtnSelectionListener = new UpDownButtonSelectionAdapter(tableViewer,
				executeChangeOrder);

// Up
		upBtn = InternalPropertySectionUtil.createButton(getWidgetFactory(), composite, true, moveBtnSelectionListener,
				"Up", SWT.PUSH);
		fd = new FormData();
		fd.left = new FormAttachment(tableComposite, ITabbedPropertyConstants.HSPACE);
		fd.top = new FormAttachment(deleteBtn, -ITabbedPropertyConstants.VMARGIN);
		fd.width = btnWidth;
		upBtn.setLayoutData(fd);

// Down
		downBtn = InternalPropertySectionUtil.createButton(getWidgetFactory(), composite, false,
				moveBtnSelectionListener, "Down", SWT.PUSH);
		fd = new FormData();
		fd.left = new FormAttachment(tableComposite, ITabbedPropertyConstants.HSPACE);
		fd.top = new FormAttachment(upBtn, -ITabbedPropertyConstants.VMARGIN);
		fd.width = btnWidth;
		downBtn.setLayoutData(fd);

		PropertySectionUtil.createSectionLabel(composite, getWidgetFactory(), "Dimensions:");
		InternalPropertySectionUtil.setPropertiesHelp(aTabbedPropertySheetPage.getControl());
	}

	private static TableViewer createTableViewer(final Composite tableComposite) {
		final TableViewer tableViewer = new TableViewer(tableComposite,
				SWT.NO_SCROLL | SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
		tableViewer.setContentProvider(new ArrayContentProvider());
		tableViewer.getTable().setLinesVisible(true);
		tableViewer.getTable().setHeaderVisible(true);

		return tableViewer;
	}

// Move call element
	private final ExecuteOrderChange executeChangeOrder = (newIndex, curIndex, dNDElement) -> {
		if (!Objects.equals(newIndex, curIndex)) {
			selectedIndex = newIndex;
			selectedBos.modify(ArrayableElement.class, ae -> {
				final ArrayDimension dim = ae.getArrayDimensions().get(dNDElement.getIndex() - 1);
				ae.getArrayDimensions().move(newIndex, dim);
			});
		}
	};

	private static ArrayDimension createArrayDimensionDuplicate(final Aadl2Package pkg, final ArrayDimension dim) {
		final ArrayDimension newDim = (ArrayDimension) pkg.getEFactoryInstance().create(pkg.getArrayDimension());
		if (dim.getSize() != null) {
			final ArraySize newArraySize = (ArraySize) pkg.getEFactoryInstance().create(pkg.getArraySize());
			newArraySize.setSize(dim.getSize().getSize());
			newArraySize.setSizeProperty(dim.getSize().getSizeProperty());
			newDim.setSize(newArraySize);
		}

		return newDim;
	}

	private ArrayDimension getSelectedDimension() {
		final IStructuredSelection selection = tableViewer.getStructuredSelection();
		if (!selection.isEmpty()) {
			final Optional<ArrayableElement> optAe = selectedBos.boStream(ArrayableElement.class).findFirst();
			if (optAe.isPresent()) {
				return optAe.get().getArrayDimensions().get(selectedIndex);
			}
		}

		return null;
	}

	private final SelectionAdapter modifyDimensionSelectionListener = new SelectionAdapter() {
		@Override
		public void widgetSelected(final SelectionEvent e) {
			selectedIndex = tableViewer.getTable().getSelectionIndex();
			final ArrayDimension readonlyDim = getSelectedDimension();
			if (readonlyDim != null) {
				final Aadl2Package pkg = Aadl2Package.eINSTANCE;
				final ArrayDimension dim = createArrayDimensionDuplicate(pkg, readonlyDim);
				selectedBos.boStream(ArrayableElement.class).findAny().ifPresent(ele -> {
					if (modifiedArrayDimension(dim, ProjectUtil.getProjectOrThrow(ele.eResource()))) {
						selectedBos.modify(ArrayableElement.class, ae -> {
							ae.getArrayDimensions().set(selectedIndex, createArrayDimensionDuplicate(pkg, dim));
						});
					}
				});
			}
		}
	};

	private final SelectionAdapter addDimensionSelectionListener = new SelectionAdapter() {
		@Override
		public void widgetSelected(final SelectionEvent e) {
			final Aadl2Package pkg = Aadl2Package.eINSTANCE;
			final ArrayDimension dim = (ArrayDimension) pkg.getEFactoryInstance().create(pkg.getArrayDimension());
			selectedBos.boStream(ArrayableElement.class).findAny().ifPresent(ele -> {
				if (modifiedArrayDimension(dim, ProjectUtil.getProjectOrThrow(ele.eResource()))) {
					selectedBos.modify(ArrayableElement.class, ae -> {
						ae.getArrayDimensions().add(createArrayDimensionDuplicate(pkg, dim));
					});
				}
			});
		}
	};

	private final SelectionAdapter deleteDimensionSelectionListener = new SelectionAdapter() {
		@Override
		public void widgetSelected(final SelectionEvent e) {
			final boolean confirmDelete = MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Confirm",
					"Would you like to delete the selected dimension?");
			if (confirmDelete) {
				final int index = tableViewer.getTable().getSelectionIndex();
				selectedBos.modify(ArrayableElement.class, ae -> {
					ae.getArrayDimensions().remove(index);
				});
			}
		}
	};

	private static TableColumnLayout createTableColumnLayout(final TableColumn column) {
		final TableColumnLayout tcl = new TableColumnLayout();
		tcl.setColumnData(column, new ColumnWeightData(1, true));
		return tcl;
	}

// Prompt user
	private static boolean modifiedArrayDimension(final ArrayDimension dim, final IProject project) {
		// Show the editor dimension dialog. If the user selects OK, it will modify the passed in object.
		final EditDimensionDialog dlg = new EditDimensionDialog(Display.getCurrent().getActiveShell(), project, dim);
		if (dlg.open() == Window.CANCEL) {
			return false;
		}

		return true;
	}

	@Override
	public void setInput(final IWorkbenchPart part, final ISelection selection) {
		super.setInput(part, selection);
		selectedBos = Adapters.adapt(selection, BusinessObjectSelection.class);
	}

	@Override
	public void refresh() {
		final List<ArrayableElement> arrayableElements = selectedBos.boStream(ArrayableElement.class)
				.collect(Collectors.toList());
		final Iterator<ArrayableElement> it = arrayableElements.iterator();
		ArrayableElement ae = it.next();
		boolean allowMultipleDimensions = !isFeature(ae);
		final List<String> arrayableDimensions = ae.getArrayDimensions()
				.stream()
				.map(ad -> getLabel(ad))
				.collect(Collectors.toList());
		boolean tableEnabled = true;
		while (it.hasNext()) {
			ae = it.next();
			if (allowMultipleDimensions) {
				allowMultipleDimensions = !isFeature(ae);
			}
			if (arrayableDimensions
					.equals(ae.getArrayDimensions().stream().map(ad -> getLabel(ad)).collect(Collectors.toList()))) {
			} else {
				// Table disabled and cleared
				tableEnabled = false;
				selectedIndex = 0;
				arrayableDimensions.clear();
				break;
			}
		}

		final int[] mutableIndex = { 0 };
		tableViewer.setInput(arrayableDimensions.stream().map(sc -> {
			mutableIndex[0] = ++mutableIndex[0];
			return new DragAndDropElement(sc, mutableIndex[0]);
		}).toArray());

		tableViewer.getTable().setSelection(selectedIndex);
		setControlsEnabled(tableEnabled, arrayableDimensions.size(), allowMultipleDimensions);
	}

	private static boolean isFeature(final ArrayableElement ae) {
		return ae instanceof Feature;
	}

	private void setControlsEnabled(final boolean tableEnabled, final int size, final boolean allowMultipleDimensions) {
		final boolean isEmpty = size == 0;
		tableViewer.getTable().setEnabled(tableEnabled);
		modifyBtn.setEnabled(!isEmpty);
		deleteBtn.setEnabled(!isEmpty);
		addBtn.setEnabled(tableEnabled && (isEmpty || allowMultipleDimensions));
		updateMoveButtons(size - 1);
	}

	private void updateMoveButtons(final int size) {
		upBtn.setEnabled(selectedIndex - 1 >= 0);
		downBtn.setEnabled(selectedIndex + 1 <= size);
	}

	private String getLabel(final ArrayDimension ad) {
		final ArraySize dimSize = ad.getSize();
		final String txt;
		if (dimSize == null) {
			txt = emptyString;
		} else if (dimSize.getSizeProperty() != null) {
			if (dimSize.getSizeProperty() instanceof Property) {
				txt = ((Property) dimSize.getSizeProperty()).getQualifiedName();
			} else if (dimSize.getSizeProperty() instanceof PropertyConstant) {
				txt = ((PropertyConstant) dimSize.getSizeProperty()).getQualifiedName();
			} else {
				txt = "<Unsupported case>";
			}
		} else {
			txt = Long.toString(dimSize.getSize());
		}

		return txt;
	}
}