package net.sourceforge.pmd.eclipse.ui.preferences.panelmanagers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.PropertySource; import net.sourceforge.pmd.eclipse.ui.PMDUiConstants; import net.sourceforge.pmd.eclipse.ui.dialogs.NewPropertyDialog; import net.sourceforge.pmd.eclipse.ui.preferences.br.EditorFactory; import net.sourceforge.pmd.eclipse.ui.preferences.br.RuleSelection; import net.sourceforge.pmd.eclipse.ui.preferences.br.RuleUtil; import net.sourceforge.pmd.eclipse.ui.preferences.br.SizeChangeListener; import net.sourceforge.pmd.eclipse.ui.preferences.br.ValueChangeListener; import net.sourceforge.pmd.eclipse.ui.preferences.editors.SWTUtil; import net.sourceforge.pmd.eclipse.util.ResourceManager; import net.sourceforge.pmd.eclipse.util.Util; import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.lang.rule.properties.factories.PropertyDescriptorUtil; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; /** * Takes in a property source instance, extracts its properties, creates a series of type-specific editors for each, and then populates * them with the current values. As some types can hold multiple values the vertical span can grow to accommodate additional widgets * and does so by broadcasting this through the SizeChange listener. The ValueChange listener can be used to update any outside UIs as * necessary. * * @author Brian Remedios */ public class FormArranger implements ValueChangeListener { private final Composite parent; private final Map<Class<?>, EditorFactory> editorFactoriesByValueType; private final ValueChangeListener changeListener; private final SizeChangeListener sizeChangeListener; private PropertySource propertySource; private Control[][] widgets; private Map<PropertyDescriptor<?>, Control[]> controlsByProperty; /** * Constructor for FormArranger. * @param theParent Composite * @param factories Map<Class,EditorFactory> */ public FormArranger(Composite theParent, Map<Class<?>, EditorFactory> factories, ValueChangeListener listener, SizeChangeListener sizeListener) { parent = theParent; editorFactoriesByValueType = factories; changeListener = chain(listener, this); sizeChangeListener = sizeListener; controlsByProperty = new HashMap<PropertyDescriptor<?>, Control[]>(); } /** * Echo the change to the second listener after notifying the primary one * * @param primaryListener * @param secondListener * @return */ public static ValueChangeListener chain(final ValueChangeListener primaryListener, final ValueChangeListener secondaryListener) { return new ValueChangeListener() { public void changed(RuleSelection rule, PropertyDescriptor<?> desc, Object newValue) { primaryListener.changed(rule, desc, newValue); secondaryListener.changed(rule, desc, newValue); } public void changed(PropertySource source, PropertyDescriptor<?> desc, Object newValue) { primaryListener.changed(source, desc, newValue); secondaryListener.changed(source, desc, newValue); } }; } protected void register(PropertyDescriptor<?> property, Control[] controls) { controlsByProperty.put(property, controls); } /** * @param desc PropertyDescriptor * @return EditorFactory */ private EditorFactory factoryFor(PropertyDescriptor<?> desc) { return editorFactoriesByValueType.get(desc.type()); } public void clearChildren() { Control[] kids = parent.getChildren(); for (Control kid : kids) kid.dispose(); parent.pack(); propertySource = null; } /** * @param theRule Rule */ public int arrangeFor(PropertySource theSource) { if (propertySource == theSource) return -1; return rearrangeFor(theSource); } public void loadValues() { rearrangeFor(propertySource); } private int rearrangeFor(PropertySource theSource) { clearChildren(); propertySource = theSource; if (propertySource == null) return -1; Map<PropertyDescriptor<?>, Object> valuesByDescriptor = Configuration.filteredPropertiesOf(propertySource); if (valuesByDescriptor.isEmpty()) { if (RuleUtil.isXPathRule(propertySource)) { addAddButton(); parent.pack(); return 1; } return 0; } PropertyDescriptor<?>[] orderedDescs = valuesByDescriptor.keySet().toArray(new PropertyDescriptor[valuesByDescriptor.size()]); Arrays.sort(orderedDescs, PropertyDescriptorUtil.COMPARATOR_BY_ORDER); int rowCount = 0; // count up the actual rows with widgets needed, not all have editors yet for (PropertyDescriptor<?> desc: orderedDescs) { EditorFactory factory = factoryFor(desc); if (factory == null) { System.out.println("No editor defined for: " + desc.getClass().getSimpleName()); continue; } rowCount++; } boolean isXPathRule = RuleUtil.isXPathRule(propertySource); int columnCount = isXPathRule ? 3 : 2; // xpath descriptors have a column of delete buttons GridLayout layout = new GridLayout(columnCount, false); layout.verticalSpacing = 2; layout.marginTop = 1; parent.setLayout(layout); widgets = new Control[rowCount][columnCount]; int rowsAdded = 0; for (PropertyDescriptor<?> desc: orderedDescs) { if (addRowWidgets( factoryFor(desc), rowsAdded, desc, isXPathRule) ) rowsAdded++; } if (RuleUtil.isXPathRule(propertySource)) { addAddButton(); rowsAdded++; } if (rowsAdded > 0) { parent.pack(); } adjustEnabledStates(); return rowsAdded; } private void addAddButton() { Button button = new Button(parent, SWT.PUSH); button.setText("Add new..."); button.addSelectionListener( new SelectionListener(){ public void widgetDefaultSelected(SelectionEvent e) { } public void widgetSelected(SelectionEvent e) { NewPropertyDialog dialog = new NewPropertyDialog(parent.getShell(), editorFactoriesByValueType, propertySource, changeListener); if (dialog.open() == Window.OK) { PropertyDescriptor<?> desc = dialog.descriptor(); propertySource.definePropertyDescriptor(desc); rearrangeFor(propertySource); } } }); } /** * @param factory EditorFactory * @param rowIndex int * @param desc PropertyDescriptor * @return boolean */ private boolean addRowWidgets(EditorFactory factory, int rowIndex, PropertyDescriptor<?> desc, boolean isXPathRule) { if (factory == null) return false; // add all the labels & controls necessary on each row widgets[rowIndex][0] = factory.addLabel(parent, desc); widgets[rowIndex][1] = factory.newEditorOn(parent, desc, propertySource, changeListener, sizeChangeListener); if (isXPathRule) { widgets[rowIndex][2] = addDeleteButton(parent, desc, propertySource, sizeChangeListener); } register(desc, widgets[rowIndex]); return true; } private Control addDeleteButton(Composite parent, final PropertyDescriptor<?> desc, final PropertySource source, final SizeChangeListener sizeChangeListener) { Button button = new Button(parent, SWT.PUSH); button.setData(desc.name()); // for later reference button.setImage(ResourceManager.imageFor(PMDUiConstants.ICON_BUTTON_DELETE)); button.addSelectionListener( new SelectionListener(){ public void widgetDefaultSelected(SelectionEvent e) { } public void widgetSelected(SelectionEvent e) { // rule.undefine(desc); rearrangeFor(source); updateDeleteButtons(); // sizeChangeListener.addedRows(-1); not necessary apres rearrange? }}); return button; } /** * Flag the delete buttons linked to property variables that are not referenced in the * Xpath source or clear any images they may have. Returns the names of any unreferenced * variables are found; */ public List<String> updateDeleteButtons() { if (propertySource == null || !RuleUtil.isXPathRule(propertySource)) { return Collections.emptyList(); } String source = propertySource.getProperty(XPathRule.XPATH_DESCRIPTOR); List<int[]> refPositions = Util.referencedNamePositionsIn(source, '$'); if (refPositions.isEmpty()) return Collections.emptyList(); List<String> unreferencedOnes = new ArrayList<String>(refPositions.size()); List<String> varNames = Util.fragmentsWithin(source, refPositions); for (Control[] widgetRow : widgets) { Button butt = (Button)widgetRow[2]; String buttonName = (String)butt.getData(); boolean isReferenced = varNames.contains(buttonName); butt.setToolTipText( isReferenced ? "Delete variable: $" + buttonName : "Delete unreferenced variable: $" + buttonName ); if (!isReferenced) unreferencedOnes.add((String) butt.getData()); } return unreferencedOnes; } private void adjustEnabledStates() { Set<PropertyDescriptor<?>> ignoreds = propertySource.ignoredProperties(); for (Map.Entry<PropertyDescriptor<?>, Control[]> entry : controlsByProperty.entrySet()) { if (ignoreds.contains( entry.getKey() )) { SWTUtil.setEnabled(entry.getValue(), false); } else { SWTUtil.setEnabled(entry.getValue(), true); } } } public void changed(RuleSelection rule, PropertyDescriptor<?> desc, Object newValue) { } public void changed(PropertySource source, PropertyDescriptor<?> desc, Object newValue) { adjustEnabledStates(); }; }