/* * Carrot2 project. * * Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński. * All rights reserved. * * Refer to the full license file "carrot2.LICENSE" * in the root folder of the repository checkout or at: * http://www.carrot2.org/carrot2.LICENSE */ package org.carrot2.workbench.core.ui; import java.text.Collator; import java.util.*; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.carrot2.core.IProcessingComponent; import org.carrot2.core.attribute.AttributeNames; import org.carrot2.util.attribute.AttributeDescriptor; import org.carrot2.util.attribute.BindableDescriptor; import org.carrot2.workbench.core.helpers.GUIFactory; import org.carrot2.workbench.core.helpers.Utils; import org.carrot2.workbench.editors.*; import org.carrot2.workbench.editors.factory.EditorFactory; import org.carrot2.workbench.editors.factory.EditorNotFoundException; import org.eclipse.jface.fieldassist.*; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; import org.carrot2.shaded.guava.common.collect.Lists; import org.carrot2.shaded.guava.common.collect.Maps; /** * An SWT composite displaying an alphabetically ordered list of {@link IAttributeEditor}s. */ public final class AttributeList extends Composite implements IAttributeEventProvider { /** * Space before the editor label (if separate). */ public final static int SPACE_BEFORE_LABEL = 5; /** * Key for enabling validation overlays (artificial attribute key for editor listeners). * * @see #setAttribute(String, Object) */ public static final String ENABLE_VALIDATION_OVERLAYS = "enable.validation.overlays"; /** * A list of {@link AttributeDescriptor}s, indexed by their keys. */ private final Map<String, AttributeDescriptor> attributeDescriptors; /** * A map between attribute keys and {@link IAttributeEditor}s visible in this * component. */ private Map<String, IAttributeEditor> editors = Maps.newHashMap(); /** * Optional component class attribute descriptors come from. */ private Class<? extends IProcessingComponent> componentClazz; /** * Attribute change listeners. */ private final List<IAttributeListener> listeners = new CopyOnWriteArrayList<IAttributeListener>(); /** * Forward events from editors to external listeners. */ private final IAttributeListener forwardListener = new ForwardingAttributeListener( listeners); /** */ private BindableDescriptor bindable; /** */ private IAttributeEventProvider globalEventsProvider; /** * Create a new editor. */ @SuppressWarnings("unchecked") public AttributeList(Composite parent, BindableDescriptor bindable, Map<String, AttributeDescriptor> attributeDescriptors, IAttributeEventProvider globalEventsProvider, Map<String, Object> currentValues) { super(parent, SWT.NONE); this.bindable = bindable; this.globalEventsProvider = globalEventsProvider; this.attributeDescriptors = attributeDescriptors; /* * Only store component clazz if it is assignable to {@link ProcessingComponent}. */ Class<?> clazz = bindable.type; if (clazz != null && IProcessingComponent.class.isAssignableFrom(clazz)) { this.componentClazz = (Class<? extends IProcessingComponent>) clazz; } createComponents(currentValues); } /** * Sets the <code>key</code> editor's current value to <code>value</code>. */ public void setAttribute(String key, Object value) { final IAttributeEditor editor = editors.get(key); if (editor != null) { editor.setValue(value); } } /* * */ public void addAttributeListener(IAttributeListener listener) { this.listeners.add(listener); } /* * */ public void removeAttributeListener(IAttributeListener listener) { this.listeners.remove(listener); } /** * */ public void dispose() { /* * Unregister listeners. */ for (IAttributeEditor editor : this.editors.values()) { editor.removeAttributeListener(forwardListener); } super.dispose(); } /** * Set the focus to the editor containing {@link AttributeNames#QUERY}, if possible. */ @Override public boolean setFocus() { if (editors.containsKey(AttributeNames.QUERY)) { editors.get(AttributeNames.QUERY).setFocus(); return true; } else return false; } /** * Create internal GUI. */ private void createComponents(Map<String, Object> currentValues) { /* * Sort alphabetically by label. */ final Locale locale = Locale.getDefault(); final Map<String, String> labels = Maps.newHashMap(); for (Map.Entry<String, AttributeDescriptor> entry : attributeDescriptors .entrySet()) { labels.put(entry.getKey(), getLabel(entry.getValue()).toLowerCase(locale)); } final Collator collator = Collator.getInstance(locale); final List<String> sortedKeys = Lists.newArrayList(labels.keySet()); Collections.sort(sortedKeys, new Comparator<String>() { public int compare(String a, String b) { return collator.compare(labels.get(a), labels.get(b)); } }); /* * Create editors and inquire about their layout needs. */ editors = Maps.newHashMap(); final Map<String, AttributeEditorInfo> editorInfos = Maps.newHashMap(); int maxColumns = 1; for (String key : sortedKeys) { final AttributeDescriptor descriptor = attributeDescriptors.get(key); IAttributeEditor editor = null; try { editor = EditorFactory.getEditorFor(this.componentClazz, descriptor); final AttributeEditorInfo info = editor.init(bindable, descriptor, globalEventsProvider, currentValues); editorInfos.put(key, info); maxColumns = Math.max(maxColumns, info.columns); } catch (EditorNotFoundException ex) { Utils.logError("No editor for attribute: " + descriptor.key + " (class: " + descriptor.type + ")", false); /* * Skip editor. */ editor = null; } editors.put(key, editor); } /* * Prepare the layout for this editor. */ final GridLayout layout = GUIFactory.zeroMarginGridLayout(); layout.makeColumnsEqualWidth = false; layout.numColumns = maxColumns; this.setLayout(layout); /* * Create visual components for editors. */ final GridDataFactory labelFactory = GridDataFactory.fillDefaults().span( maxColumns, 1); boolean firstEditor = true; for (String key : sortedKeys) { final AttributeDescriptor descriptor = attributeDescriptors.get(key); final IAttributeEditor editor = editors.get(key); final AttributeEditorInfo editorInfo = editorInfos.get(key); if (editor == null) { // Skip attributes without the editor. continue; } final Object defaultValue; if (currentValues != null && currentValues.get(key) != null) { defaultValue = currentValues.get(key); } else { defaultValue = attributeDescriptors.get(key).defaultValue; } // Add label to editors that do not have it. if (!editorInfo.displaysOwnLabel) { final Label label = new Label(this, SWT.LEAD); final GridData gd = labelFactory.create(); if (!firstEditor) { gd.verticalIndent = SPACE_BEFORE_LABEL; } label.setLayoutData(gd); label.setText(getLabel(descriptor) + (descriptor.requiredAttribute ? " (required)" : "")); /* * Add validation overlay. */ addValidationOverlay(descriptor, editor, defaultValue, label); AttributeInfoTooltip.attach(label, descriptor); } // Add the editor, if available. editor.createEditor(this, maxColumns); // Set the default value for the editor. editor.setValue(defaultValue); editors.put(editor.getAttributeKey(), editor); /* * Forward events from this editor to all our listeners. */ editor.addAttributeListener(forwardListener); firstEditor = false; } } /** * Adds validation overlay component to the control. */ private void addValidationOverlay(final AttributeDescriptor descriptor, final IAttributeEditor editor, final Object defaultValue, final Control label) { final ControlDecoration decoration = new ControlDecoration(label, SWT.LEFT | SWT.BOTTOM); decoration.hide(); final FieldDecoration fieldDecoration = FieldDecorationRegistry .getDefault().getFieldDecoration( FieldDecorationRegistry.DEC_ERROR); decoration.setImage(fieldDecoration.getImage()); decoration.setDescriptionText("Invalid value"); final IAttributeListener validationListener = new InvalidStateDecorationListener( decoration, descriptor, defaultValue); globalEventsProvider.addAttributeListener(validationListener); editor.addAttributeListener(validationListener); label.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { globalEventsProvider.removeAttributeListener(validationListener); editor.removeAttributeListener(validationListener); decoration.dispose(); } }); } /* * */ private String getLabel(AttributeDescriptor descriptor) { String text = null; if (descriptor.metadata != null) { text = descriptor.metadata.getLabelOrTitle(); } if (text == null) { text = "(no label available)"; } return text; } }