/******************************************************************************* * Copyright (c) 2014, 2015 Formal Mind GmbH, University of Duesseldorf and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Michael Jastram - initial API and implementation *******************************************************************************/ package org.eclipse.rmf.reqif10.pror.editor.propertiesview; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.TreeMap; import java.util.TreeSet; import org.agilemore.agilegrid.AbstractContentProvider; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.edit.provider.IItemPropertyDescriptor; import org.eclipse.emf.edit.provider.ItemPropertyDescriptor.PropertyValueWrapper; import org.eclipse.emf.edit.provider.ItemProviderAdapter; import org.eclipse.rmf.reqif10.AttributeDefinition; import org.eclipse.rmf.reqif10.AttributeValue; import org.eclipse.rmf.reqif10.ReqIF10Package; import org.eclipse.rmf.reqif10.SpecElementWithAttributes; import org.eclipse.rmf.reqif10.SpecHierarchy; import org.eclipse.rmf.reqif10.common.util.ReqIF10Util; import org.eclipse.rmf.reqif10.pror.util.ProrUtil; /** * The agile grid content provider for the properties view. Internally, it * manages a List of PropertyRows. These represent Categories and Descriptors * (for AttributeValues and EMF Values) * * @author Lukas Ladenberger * @author Michael Jastram * */ public class ProrPropertyContentProvider extends AbstractContentProvider implements Adapter { // Special categories that should be ordered differently public static String SPEC_HIERARCHY_NAME = "Spec Hierarchy"; public static String SPEC_OBJECT_NAME = "Spec Object"; public static String SPEC_RELATION_NAME = "Spec Relation"; public static String SPECIFICATION_NAME = "Specification"; public static String RELATION_GROUP_NAME = "Relation Group"; // Only access via getRows(), to ensure it's not null. private List<PropertyRow> rows; // The Object whose properties are shown. private Object content; // Whether to show all properties or not private boolean showAllProps; private AdapterFactory adapterFactory; public ProrPropertyContentProvider(AdapterFactory adapterFactory, boolean showAllProps) { this.adapterFactory = adapterFactory; this.showAllProps = showAllProps; } @Override public Object doGetContentAt(int row, int col) { return getRows().get(row).getContent(col); } /** * This is the object for which this contentProvider manges the properties. * It is either a {@link SpecElementWithAttributes} or a * {@link SpecHierarchy}. */ protected Object getElement() { return content; } @Override public void doSetContentAt(int row, int col, Object value) { // We don't need this method, cause an automatic refresh of the agile // grid after editing a cell // Fix of 378041: // needed to inform ProrPropertyControl of property change. // firePropertyChange is never called from // AbstractContentProvider.setContentAt(..) // because oldValue is equal to newValue (value already updated // somewhere else?) super.firePropertyChange("", null, null); } public PropertyRow getRowContent(int row) { if (row >= getRowCount()) return null; return getRows().get(row); } /** * Besides setting the content, we register this as an Adapter to react to content changes. */ public void setContent(Object content) { if (content == this.content) return; if (this.content instanceof EObject) { EObject eobj = (EObject) this.content; eobj.eAdapters().remove(this); } if (content instanceof EObject) { EObject eobj = (EObject) content; eobj.eAdapters().add(this); } this.content = content; rows = null; } public int getRowCount() { return getRows().size(); } /** * Lazily builds a list of {@link PropertyRow}s, representing either * {@link Category}s or {@link PropertyRow}s. These are properly ordered. * * @return */ protected List<PropertyRow> getRows() { // Use cached version if it exists. if (rows != null) return rows; // We get the provider, which may be null. rows = new ArrayList<PropertyRow>(); ItemProviderAdapter provider = ProrUtil.getItemProvider(adapterFactory, content); if (provider == null) return rows; // To ensure ordering by category and then alphabetically, we build a // map of sets. // As Descriptors are Comparable, they are properly ordered. TreeMap<String, TreeSet<Descriptor>> categoryMap = new TreeMap<String, TreeSet<Descriptor>>(); for (IItemPropertyDescriptor prop : provider .getPropertyDescriptors(content)) { if (!showAllProps && isAdvancedProperty(prop)) continue; String categoryName = prop.getCategory(content); if (categoryName == null) { categoryName = "Misc"; } TreeSet<Descriptor> categorySet = categoryMap.get(categoryName); if (categorySet == null) { categorySet = new TreeSet<Descriptor>(); categoryMap.put(categoryName, categorySet); } categorySet.add(new Descriptor(prop)); } // To ensure user-relevant ordering, we add the following categories in // this order... addCategoryAndRemoveFromMap(categoryMap, SPEC_OBJECT_NAME); addCategoryAndRemoveFromMap(categoryMap, SPEC_RELATION_NAME); addCategoryAndRemoveFromMap(categoryMap, SPEC_HIERARCHY_NAME); addCategoryAndRemoveFromMap(categoryMap, SPECIFICATION_NAME); addCategoryAndRemoveFromMap(categoryMap, RELATION_GROUP_NAME); // ... and insert all other categories before. for (String categoryName : categoryMap.keySet()) { rows.add(0, new Category(categoryName)); rows.addAll(1, categoryMap.get(categoryName)); } return rows; } private void addCategoryAndRemoveFromMap( TreeMap<String, TreeSet<Descriptor>> categoryMap, String categoryName) { if (categoryMap.containsKey(categoryName)) { rows.add(new Category(categoryName)); rows.addAll(categoryMap.get(categoryName)); categoryMap.remove(categoryName); } } /** * Return true if this is one of the special properties that should only be * shown on the advanced property panel. * * @param prop */ private boolean isAdvancedProperty(IItemPropertyDescriptor prop) { String name = prop.getId(content); String category = prop.getCategory(content); // Special case: Datatype Dialog if (category == null && "identifier".equals(name) || "desc".equals(name) || "lastChange".equals(name)) { return true; } // Only hide properties that belong to the standard categories if (!(SPEC_HIERARCHY_NAME.equals(category) || SPEC_RELATION_NAME.equals(category) || SPEC_OBJECT_NAME.equals(category) || SPECIFICATION_NAME.equals(category) || RELATION_GROUP_NAME .equals(category))) { return false; } // These are the attributes that shall be hidden. if ("identifier".equals(name) || "desc".equals(name) || "lastChange".equals(name) || "editable".equals(name) || "longName".equals(name) || "tableInternal".equals(name) || "object".equals(name) || "editableAtts".equals(name)) { return true; } return false; } /** * Three implementations of this interface are provided to represent the * rows of the Property View. */ public interface PropertyRow extends Comparable<PropertyRow> { Object getContent(int column); } /** * Rows representing a Category */ public class Category implements PropertyRow { String name; public Category(String name) { this.name = name; } public Object getContent(int column) { return column == 0 ? name : null; } public int compareTo(PropertyRow that) { return name.compareTo(((Category) that).name); } @Override public boolean equals(Object that) { if (!(that instanceof Category)) return false; return compareTo((PropertyRow) that) == 0; } @Override public int hashCode() { return name.hashCode(); } } /** * Rows representing an {@link IItemPropertyDescriptor}. This can one that * is RMF-Specific or EMF-Specific. */ public class Descriptor implements PropertyRow { IItemPropertyDescriptor descriptor; AttributeValue attributeValue; public Descriptor(IItemPropertyDescriptor descriptor) { this.descriptor = descriptor; } public IItemPropertyDescriptor getItemPropertyDescriptor() { return descriptor; } public boolean isRMFSpecific() { Object feature = descriptor.getFeature(content); return feature == ReqIF10Package.Literals.SPEC_ELEMENT_WITH_ATTRIBUTES__VALUES || descriptor.getFeature(content) == ReqIF10Package.Literals.ATTRIBUTE_VALUE_BOOLEAN__THE_VALUE || descriptor.getFeature(content) == ReqIF10Package.Literals.ATTRIBUTE_VALUE_DATE__THE_VALUE || descriptor.getFeature(content) == ReqIF10Package.Literals.ATTRIBUTE_VALUE_ENUMERATION__VALUES || descriptor.getFeature(content) == ReqIF10Package.Literals.ATTRIBUTE_VALUE_INTEGER__THE_VALUE || descriptor.getFeature(content) == ReqIF10Package.Literals.ATTRIBUTE_VALUE_REAL__THE_VALUE || descriptor.getFeature(content) == ReqIF10Package.Literals.ATTRIBUTE_VALUE_STRING__THE_VALUE || descriptor.getFeature(content) == ReqIF10Package.Literals.ATTRIBUTE_VALUE_XHTML__THE_VALUE; } public Object getContent(int column) { return column == 0 ? descriptor.getDisplayName(content) : descriptor.getPropertyValue(content); } public int compareTo(PropertyRow that) { return descriptor.getDisplayName(content).compareTo( ((Descriptor) that).descriptor.getDisplayName(content)); } /** * Returns the string content from the descriptor. However, we have to extract * the string differently for {@link GregorianCalendar}. */ @Override public String toString() { PropertyValueWrapper propertyValueWrapper = (PropertyValueWrapper) descriptor .getPropertyValue(content); if (propertyValueWrapper != null) { Object editableValue = propertyValueWrapper.getEditableValue(content); if (editableValue instanceof GregorianCalendar) { GregorianCalendar cal = (GregorianCalendar) editableValue; Date date = cal.getTime(); return DateFormat.getDateTimeInstance().format(date); } else { return propertyValueWrapper .getText(content); } } return ""; } @Override public boolean equals(Object that) { if (!(that instanceof Descriptor)) return false; return compareTo((Descriptor) that) == 0; } @Override public int hashCode() { return descriptor.hashCode(); } /** * It is quite possible that the AttributeValue does not exist yet. In * that case, a new AttributeValue is created and returned, but not * connected to the parent object (i.e. EObject#eContainer() returns * null). */ public AttributeValue getAttributeValue() { if (!isRMFSpecific()) return null; // Find the SpecElement SpecElementWithAttributes specElement = null; if (content instanceof SpecElementWithAttributes) { specElement = (SpecElementWithAttributes) content; } else if (content instanceof SpecHierarchy) { specElement = ((SpecHierarchy) content).getObject(); } else if (content instanceof AttributeValue) { return (AttributeValue) content; } // Could also be a default value if (specElement == null) { if (content instanceof AttributeDefinition) { AttributeDefinition ad = (AttributeDefinition) content; EStructuralFeature defaultValueFeature = ReqIF10Util.getDefaultValueFeature(ad); return (AttributeValue) ((AttributeDefinition) content).eGet(defaultValueFeature); } } return ReqIF10Util.getAttributeValueForLabel(specElement, descriptor.getDisplayName(specElement)); } } ////////////////////////////////////////////////////////////////////////////////// // Methods from Interface Adapter ////////////////////////////////////////////////////////////////////////////////// /** * We register this object with the content in #setContent(). Upon a notification, * we fire a property change to inform the agileGrid to update itself. */ public void notifyChanged(Notification notification) { firePropertyChange("", null, content); } // Not used. public Notifier getTarget() { return null; } // Not used. public void setTarget(Notifier newTarget) { } // Not used. public boolean isAdapterForType(Object type) { return false; } public PropertyRow getRowContent(int row, int col) { return getRowContent(row); } }