0001: package net.refractions.udig.ui;
0002:
0003: import java.lang.reflect.Array;
0004: import java.util.Collection;
0005: import java.util.Collections;
0006: import java.util.Comparator;
0007: import java.util.List;
0008: import java.util.Set;
0009: import java.util.concurrent.CopyOnWriteArraySet;
0010: import java.util.regex.Pattern;
0011: import java.util.regex.PatternSyntaxException;
0012:
0013: import net.refractions.udig.core.IProvider;
0014: import net.refractions.udig.internal.ui.Trace;
0015: import net.refractions.udig.internal.ui.UiPlugin;
0016: import net.refractions.udig.ui.internal.Messages;
0017:
0018: import org.eclipse.core.runtime.IAdaptable;
0019: import org.eclipse.core.runtime.IProgressMonitor;
0020: import org.eclipse.jface.action.MenuManager;
0021: import org.eclipse.jface.dialogs.MessageDialogWithToggle;
0022: import org.eclipse.jface.preference.IPreferenceStore;
0023: import org.eclipse.jface.viewers.CellEditor;
0024: import org.eclipse.jface.viewers.ColumnWeightData;
0025: import org.eclipse.jface.viewers.IBaseLabelProvider;
0026: import org.eclipse.jface.viewers.ICellEditorListener;
0027: import org.eclipse.jface.viewers.ICellEditorValidator;
0028: import org.eclipse.jface.viewers.ICellModifier;
0029: import org.eclipse.jface.viewers.IContentProvider;
0030: import org.eclipse.jface.viewers.ISelection;
0031: import org.eclipse.jface.viewers.ISelectionChangedListener;
0032: import org.eclipse.jface.viewers.ISelectionProvider;
0033: import org.eclipse.jface.viewers.StructuredSelection;
0034: import org.eclipse.jface.viewers.TableLayout;
0035: import org.eclipse.jface.viewers.TableViewer;
0036: import org.eclipse.jface.viewers.TextCellEditor;
0037: import org.eclipse.swt.SWT;
0038: import org.eclipse.swt.graphics.Color;
0039: import org.eclipse.swt.widgets.Composite;
0040: import org.eclipse.swt.widgets.Control;
0041: import org.eclipse.swt.widgets.Display;
0042: import org.eclipse.swt.widgets.Event;
0043: import org.eclipse.swt.widgets.Listener;
0044: import org.eclipse.swt.widgets.Menu;
0045: import org.eclipse.swt.widgets.Table;
0046: import org.eclipse.swt.widgets.TableColumn;
0047: import org.eclipse.swt.widgets.Text;
0048: import org.eclipse.ui.part.PageBook;
0049: import org.geotools.feature.AttributeType;
0050: import org.geotools.feature.Feature;
0051: import org.geotools.feature.FeatureCollection;
0052: import org.geotools.feature.FeatureType;
0053: import org.geotools.filter.FidFilter;
0054:
0055: import com.vividsolutions.jts.geom.Geometry;
0056:
0057: /**
0058: * A TreeViewer control for viewing a table of Feature attributes.
0059: * <p>
0060: * The object is used by using a FeatureCollection. In this case the control hangs on to a reference
0061: * to the FeatureCollection and populates the table entries directory from it. This method results
0062: * in a single page containing all features.
0063: * </p>
0064: * <p>
0065: * If the FeatureCollection implements the {@link IAdaptable} interface and adapts to
0066: * {@link ICellModifier} then the table is editable. The {@link ICellModifier} is used to modify the
0067: * features. The Column properties passed to the {@link ICellModifier} are the attribute name of the
0068: * attribute being modified.
0069: * </p>
0070: * <p>
0071: * If the FeatureCollection implements the {@link IAdaptable} interface and adapts to
0072: * {@link CellEditor[]} then the cell editors will be used to edit the cells. This is optional for
0073: * editing. By default a {@link TextCellEditor} is used for editing most cells, and an
0074: * {@link AttributeValidator} is used to validate the new values. The first column is for the fid
0075: * column and will not be used since FIDS are assigned by the datastore and can not be modified. The
0076: * number of Items the array (this is the same for the cell editor validators and cell editor
0077: * listeners) must be either the number of attributes in the feature type or the number of
0078: * attributes + 1 (one for the FID column). If the number of editors it Attributes+1 then the first
0079: * element in the array will not be used as it is assumed to be a placeholder for the fid column.
0080: * </p>
0081: * <p>
0082: * If the FeatureCollection implements the {@link IAdaptable} interface and adapts to
0083: * {@link ICellEditorValidator[]} then the validators will be used to validate the cells.
0084: * </p>
0085: * <p>
0086: * If the FeatureCollection implements the {@link IAdaptable} interface and adapts to
0087: * {@link ICellEditorListener[]} then the listeners will be added to the {@link CellEditor}s.
0088: * </p>
0089: *
0090: * @author jdeolive
0091: * @author jeichar
0092: * @since 0.3
0093: */
0094: public class FeatureTableControl implements ISelectionProvider {
0095:
0096: public static final String FEATURE_ID_COLUMN_PROPERTY = "FeatureIDProperty"; //$NON-NLS-1$
0097:
0098: public static final Object ERROR_COLUMN_PROPERTY = "ErrorProperty"; //$NON-NLS-1$
0099:
0100: public static final Object LOADING = new Object();
0101:
0102: /** results per page * */
0103: private int pageSize = 10; // XXX: actual put this as a user pref
0104:
0105: /** table viewer control * */
0106: private TableViewer tableViewer;
0107:
0108: private PageBook book;
0109:
0110: private Text message;
0111:
0112: FeatureCollection features;
0113:
0114: private final IProvider<IProgressMonitor> progressMonitorProvider;
0115:
0116: private FeatureTableSelectionProvider selectionProvider;
0117:
0118: private Color messageBackground;
0119:
0120: private Color messageForeground;
0121:
0122: private Set<IFeatureTableLoadingListener> loadingListeners = new CopyOnWriteArraySet<IFeatureTableLoadingListener>();
0123:
0124: private Comparator<Feature> currentComparator;
0125:
0126: private MenuManager contextMenu;
0127:
0128: /**
0129: * Construct <code>FeatureTableControl</code>.
0130: * <p>
0131: * Must call setFeatures before use.
0132: * </p>
0133: */
0134: public FeatureTableControl() {
0135: this (ProgressManager.instance());
0136: }
0137:
0138: /**
0139: * Construct a <code>FeatureTableControl</code>.
0140: *
0141: * @param monitorProvider a provider that will provider progress monitors for displaying loading
0142: * information.
0143: * @param fReader The FeatureReader that returns the actual features.
0144: * @param resPerPage Results per page to be shown in the table.
0145: */
0146: public FeatureTableControl(Composite parent,
0147: FeatureCollection features) {
0148: this (ProgressManager.instance(), parent, features);
0149: }
0150:
0151: /**
0152: * Construct <code>FeatureTableControl</code>.
0153: * <p>
0154: * Must call setFeatures before use.
0155: * </p>
0156: *
0157: * @param monitorProvider a provider that will provider progress monitors for displaying loading
0158: * information.
0159: */
0160: public FeatureTableControl(
0161: final IProvider<IProgressMonitor> monitorProvider) {
0162: this .progressMonitorProvider = monitorProvider;
0163: this .selectionProvider = new FeatureTableSelectionProvider(
0164: this , ProgressManager.instance());
0165: }
0166:
0167: /**
0168: * Construct a <code>FeatureTableControl</code>.
0169: *
0170: * @param monitorProvider a provider that will provider progress monitors for displaying loading
0171: * information.
0172: * @param fReader The FeatureReader that returns the actual features.
0173: * @param resPerPage Results per page to be shown in the table.
0174: */
0175: public FeatureTableControl(
0176: final IProvider<IProgressMonitor> monitorProvider,
0177: Composite parent, FeatureCollection features) {
0178: this (monitorProvider);
0179: this .features = features;
0180: createTableControl(parent);
0181: }
0182:
0183: /**
0184: * Sets the number of features viewed in the table per page.
0185: *
0186: * @param resPerPage positive integer.
0187: */
0188: public void setPageSize(int resPerPage) {
0189: this .pageSize = resPerPage;
0190: }
0191:
0192: /**
0193: * Returns the number of features viewed in the table per page.
0194: *
0195: * @return positive integer.
0196: */
0197: public int getPageSize() {
0198: return pageSize;
0199: }
0200:
0201: /**
0202: * Returns the control representing the table control.
0203: *
0204: * @return The internal table viewer control.
0205: */
0206: public Control getControl() {
0207: return book;
0208: }
0209:
0210: public void dispose() {
0211: disposeTableViewer();
0212: }
0213:
0214: /**
0215: * Creates the table control.
0216: *
0217: * @param parent The to be parent of the control.
0218: */
0219: public void createTableControl(Composite parent) {
0220: showWarning(parent.getDisplay());
0221: book = new PageBook(parent, SWT.NONE);
0222: message = new Text(book, SWT.WRAP);
0223: messageBackground = message.getBackground();
0224: messageForeground = message.getForeground();
0225: createTableViewer(book);
0226: }
0227:
0228: /**
0229: * Key for indicating whether the warning should be displayed. false if the warning is displayed
0230: */
0231: public static final String CACHING_WARNING = "FEATURE_TABLE_CACHING_IN_MEMORY_WARNING"; //$NON-NLS-1$
0232:
0233: /**
0234: * Indicates that all attribute types will be searched by the select method
0235: * @see #select(String, String[], boolean)
0236: */
0237: public static final String[] ALL = new String[0];
0238:
0239: private static final boolean SHOW_PATH = false;
0240:
0241: private void showWarning(Display display) {
0242:
0243: IPreferenceStore preferenceStore = UiPlugin.getDefault()
0244: .getPreferenceStore();
0245: if (!preferenceStore.getBoolean(CACHING_WARNING)) {
0246: MessageDialogWithToggle dialog = MessageDialogWithToggle
0247: .openWarning(
0248: display.getActiveShell(),
0249: Messages.FeatureTableControl_warningTitle,
0250: Messages.FeatureTableControl_warningMessage,
0251: Messages.FeatureTableControl_warningToggle,
0252: false, null, null);
0253: preferenceStore.setValue(CACHING_WARNING, dialog
0254: .getToggleState());
0255:
0256: }
0257:
0258: }
0259:
0260: /**
0261: * Updates the table control with the current set of features.
0262: * <p>
0263: * This method will ensure that the column information gets updated
0264: * </p>
0265: */
0266: public void update() {
0267: checkWidget();
0268: if (tableViewer != null) {
0269: tableViewer.setInput(features);
0270: tableViewer.getTable().clearAll();
0271: }
0272: }
0273:
0274: /**
0275: * Creates the table control itself.
0276: *
0277: * @param parent
0278: */
0279: protected void createTableViewer(Composite parent) {
0280: int style = SWT.FULL_SELECTION | SWT.VIRTUAL | SWT.H_SCROLL
0281: | SWT.V_SCROLL;
0282: if (tableViewer != null) {
0283: disposeTableViewer();
0284: }
0285: final Table table = new Table(parent, style);
0286: table.setLinesVisible(true);
0287: TableLayout layout = new TableLayout();
0288: table.setLayout(layout);
0289:
0290: FeatureTableContentProvider ftp = new FeatureTableContentProvider(
0291: this , this .progressMonitorProvider);
0292: FeatureTableLabelProvider flp = new FeatureTableLabelProvider(
0293: this );
0294: tableViewer = new TableViewer(table);
0295:
0296: tableViewer.setContentProvider(ftp);
0297: tableViewer.setLabelProvider(flp);
0298:
0299: // create columns after tableViewer is created because Column listeners need to access the
0300: // tableViewer.
0301: createAttributeColumns(table, tableViewer, layout);
0302: table.setHeaderVisible(true);
0303:
0304: addSelectionListener(table);
0305: if (features instanceof IAdaptable
0306: && ((IAdaptable) features)
0307: .getAdapter(ICellModifier.class) != null) {
0308:
0309: IAdaptable adaptable = (IAdaptable) features;
0310: FeatureType schema = features.getSchema();
0311: int attributeCount = schema.getAttributeCount();
0312: setCellEditors(adaptable, attributeCount);
0313:
0314: setCellValidators(adaptable);
0315: addCellEditorListeners(adaptable);
0316: tableViewer.setCellModifier((ICellModifier) adaptable
0317: .getAdapter(ICellModifier.class));
0318:
0319: String[] properties = new String[attributeCount + 1];
0320: for (int i = 0; i < properties.length; i++) {
0321: if (i == 0)
0322: properties[i] = FEATURE_ID_COLUMN_PROPERTY;
0323: else {
0324: properties[i] = schema.getAttributeType(i - 1)
0325: .getName();
0326: }
0327: }
0328: tableViewer.setColumnProperties(properties);
0329: }
0330: if (contextMenu != null) {
0331: Menu menu = contextMenu.createContextMenu(tableViewer
0332: .getControl());
0333: tableViewer.getControl().setMenu(menu);
0334: }
0335: book.showPage(tableViewer.getControl());
0336:
0337: UiPlugin
0338: .trace(
0339: Trace.FEATURE_TABLE,
0340: getClass(),
0341: "createTableViewer(): showing table View", SHOW_PATH ? new Exception() : null); //$NON-NLS-1$
0342:
0343: if (features != null) {
0344: tableViewer.setInput(features);
0345: }
0346:
0347: }
0348:
0349: private void addSelectionListener(final Table table) {
0350: // We are not using default selection provided by the table because it is too slow
0351: // so I am doing my own listening and based on which items are selected and what
0352: // keys are down I am simulating the selection behaviour.
0353: table.addListener(SWT.MouseDown, new Listener() {
0354:
0355: int lastIndex = -1;
0356:
0357: public void handleEvent(Event e) {
0358:
0359: int index = table.getSelectionIndex();
0360: FeatureTableContentProvider provider = (FeatureTableContentProvider) tableViewer
0361: .getContentProvider();
0362: Collection<String> selectionFids = selectionProvider
0363: .getSelectionFids();
0364:
0365: table.deselect(index);
0366: if ((e.stateMask & SWT.MOD2) != 0 && lastIndex != -1) {
0367: if (lastIndex == index)
0368: return;
0369: handleSelecteRange(table, index, provider,
0370: selectionFids);
0371: } else if ((e.stateMask & SWT.MOD1) != 0) {
0372: handleXORSelect(table, index, provider,
0373: selectionFids);
0374: } else {
0375: if (lastIndex == index)
0376: return;
0377: handleDefault(table, index, provider, selectionFids);
0378: }
0379:
0380: selectionProvider.notifyListeners();
0381: }
0382:
0383: private void handleDefault(final Table table, int index,
0384: FeatureTableContentProvider provider,
0385: Collection<String> selectionFids) {
0386: if (index == -1) {
0387: selectionFids.clear();
0388: table.clearAll();
0389: } else {
0390: String fid = provider.features.get(index).getID();
0391: selectionFids.clear();
0392: selectionFids.add(fid);
0393: table.clearAll();
0394: }
0395: lastIndex = index;
0396: }
0397:
0398: private void handleXORSelect(final Table table, int index,
0399: FeatureTableContentProvider provider,
0400: Collection<String> selectionFids) {
0401: String fid = provider.features.get(index).getID();
0402: if (selectionFids.contains(fid)) {
0403: selectionFids.remove(fid);
0404: } else {
0405: selectionFids.add(fid);
0406: }
0407: table.clear(index);
0408: lastIndex = index;
0409: }
0410:
0411: private void handleSelecteRange(final Table table,
0412: int index, FeatureTableContentProvider provider,
0413: Collection<String> selectionFids) {
0414: selectionFids.clear();
0415:
0416: int low = Math.min(lastIndex, index);
0417: int high = Math.max(lastIndex, index);
0418: List<Feature> toAdd = provider.features.subList(low,
0419: high + 1);
0420: boolean foundUnselectedItem = false;
0421: int i = low;
0422: for (Feature feature : toAdd) {
0423: if (selectionFids.add(feature.getID())) {
0424: foundUnselectedItem = true;
0425: }
0426:
0427: i++;
0428: }
0429: if (foundUnselectedItem)
0430: table.clearAll();
0431: }
0432:
0433: });
0434: }
0435:
0436: private void disposeTableViewer() {
0437: if (tableViewer == null)
0438: return;
0439: IContentProvider contentProvider = tableViewer
0440: .getContentProvider();
0441: if (contentProvider != null)
0442: contentProvider.dispose();
0443: IBaseLabelProvider labelProvider = tableViewer
0444: .getLabelProvider();
0445: if (labelProvider != null)
0446: labelProvider.dispose();
0447: Control control = tableViewer.getControl();
0448: if (control != null)
0449: control.dispose();
0450: tableViewer = null;
0451: }
0452:
0453: private void setCellEditors(IAdaptable adaptable, int attributeCount) {
0454: if (adaptable.getAdapter(CellEditor[].class) != null) {
0455: CellEditor[] editors = (CellEditor[]) adaptable
0456: .getAdapter(Array.class);
0457: if (editors.length < attributeCount) {
0458: UiPlugin
0459: .log(
0460: "not enough cell editors for feature type so not used", new Exception()); //$NON-NLS-1$
0461: createCellEditors();
0462: } else {
0463: CellEditor[] copy = new CellEditor[editors.length + 1];
0464: if (editors.length == attributeCount) {
0465: // there is an editor for each attribute. First element in copy if for the
0466: // fid column (which is not editable).
0467: System.arraycopy(editors, 0, copy, 1,
0468: attributeCount);
0469: } else {
0470:
0471: // ignore 1st element in editors because it is for the FID column which is read
0472: // only.
0473: System.arraycopy(editors, 1, copy, 1,
0474: attributeCount);
0475:
0476: }
0477: tableViewer.setCellEditors(copy);
0478: }
0479: } else {
0480: createCellEditors();
0481: }
0482: }
0483:
0484: private void addCellEditorListeners(IAdaptable adaptable) {
0485: CellEditor[] editors = tableViewer.getCellEditors();
0486: // offset is usually 1 but if the number of validators==number of attributes then the offset
0487: // is 0
0488: // because the first column is the FID column which doesn't have a listener.
0489: int offset = 1;
0490:
0491: ICellEditorListener[] listener = null;
0492: if (adaptable.getAdapter(ICellEditorListener[].class) != null) {
0493: listener = (ICellEditorListener[]) adaptable
0494: .getAdapter(ICellEditorListener[].class);
0495: int attributeCount = features.getSchema()
0496: .getAttributeCount();
0497: if (listener.length < attributeCount) {
0498: UiPlugin
0499: .log(
0500: "not enough cell editors for feature type so not used", new Exception()); //$NON-NLS-1$
0501: return;
0502: } else if (listener.length == attributeCount + 1) {
0503: offset = 0;
0504: }
0505: }
0506:
0507: for (int i = 0; i < editors.length - offset; i++) {
0508: final CellEditor editor = editors[i + offset];
0509: if (editor == null)
0510: continue;
0511: if (listener != null && listener[i] != null)
0512: editor.addListener(listener[i]);
0513: editor.addListener(new DisplayErrorCellListener(editor));
0514: }
0515: }
0516:
0517: private void setCellValidators(IAdaptable adaptable) {
0518: CellEditor[] editors = tableViewer.getCellEditors();
0519: FeatureType schema = features.getSchema();
0520: // offset is usually 1 but if the number of validators==number of attributes then the offset
0521: // is 0
0522: // because the first column is the FID column which doesn't have a listener.
0523: int offset = 1;
0524:
0525: ICellEditorValidator[] validators = null;
0526: if (adaptable.getAdapter(ICellEditorValidator[].class) != null) {
0527: validators = (ICellEditorValidator[]) adaptable
0528: .getAdapter(ICellEditorValidator[].class);
0529: int attributeCount = features.getSchema()
0530: .getAttributeCount();
0531: if (validators.length < attributeCount) {
0532: UiPlugin
0533: .log(
0534: "not enough cell editors for feature type so not used", new Exception()); //$NON-NLS-1$
0535: validators = null;
0536: } else if (validators.length == attributeCount) {
0537: offset = 0;
0538: }
0539: }
0540:
0541: for (int i = 0; i < editors.length - offset; i++) {
0542: CellEditor editor = editors[i + offset];
0543: if (editor == null)
0544: continue;
0545: if (validators != null && validators[i] != null)
0546: editor.setValidator(validators[i]);
0547: else
0548: editor.setValidator(new AttributeValidator(schema
0549: .getAttributeType(i), schema));
0550: }
0551: }
0552:
0553: @SuppressWarnings("unchecked")
0554: private void createCellEditors() {
0555: FeatureType schema = features.getSchema();
0556: org.eclipse.jface.viewers.CellEditor[] editors = new org.eclipse.jface.viewers.CellEditor[schema
0557: .getAttributeCount() + 1];
0558:
0559: for (int i = 0; i < schema.getAttributeCount(); i++) {
0560: AttributeType aType = schema.getAttributeType(i);
0561: Class<? extends Object> concreteType = aType.getType();
0562: Composite control = (Composite) tableViewer.getControl();
0563: if (concreteType.isAssignableFrom(String.class)) {
0564: BasicTypeCellEditor textCellEditor = new BasicTypeCellEditor(
0565: control, String.class);
0566: editors[i + 1] = textCellEditor;
0567: } else if (concreteType.isAssignableFrom(Integer.class)) {
0568: BasicTypeCellEditor textCellEditor = new BasicTypeCellEditor(
0569: control, Integer.class);
0570: editors[i + 1] = textCellEditor;
0571: } else if (concreteType.isAssignableFrom(Double.class)) {
0572: BasicTypeCellEditor textCellEditor = new BasicTypeCellEditor(
0573: control, Double.class);
0574: editors[i + 1] = textCellEditor;
0575: } else if (concreteType.isAssignableFrom(Float.class)) {
0576: BasicTypeCellEditor textCellEditor = new BasicTypeCellEditor(
0577: control, Float.class);
0578: editors[i + 1] = textCellEditor;
0579:
0580: } else if (concreteType.isAssignableFrom(Boolean.class)) {
0581: BooleanCellEditor textCellEditor = new BooleanCellEditor(
0582: control);
0583: editors[i + 1] = textCellEditor;
0584:
0585: } else if (concreteType.isAssignableFrom(Character.class)) {
0586: BasicTypeCellEditor textCellEditor = new BasicTypeCellEditor(
0587: control, Character.class);
0588: editors[i + 1] = textCellEditor;
0589:
0590: }
0591: // else if( concreteType.isAssignableFrom(Date.class)){
0592: // WarningCellEditor textCellEditor = new WarningCellEditor(control, "The Date type does
0593: // not yet have a editor, please make a bug report for this Attribute Type");
0594: // editors[i+1]=textCellEditor;
0595: //
0596: // }
0597: else if (concreteType.isAssignableFrom(Byte.class)) {
0598: BasicTypeCellEditor textCellEditor = new BasicTypeCellEditor(
0599: control, Byte.class);
0600: editors[i + 1] = textCellEditor;
0601:
0602: } else if (concreteType.isAssignableFrom(Short.class)) {
0603: BasicTypeCellEditor textCellEditor = new BasicTypeCellEditor(
0604: control, Short.class);
0605: editors[i + 1] = textCellEditor;
0606:
0607: } else if (concreteType.isAssignableFrom(Long.class)) {
0608: BasicTypeCellEditor textCellEditor = new BasicTypeCellEditor(
0609: control, Long.class);
0610: editors[i + 1] = textCellEditor;
0611:
0612: } else {
0613: WarningCellEditor textCellEditor = new WarningCellEditor(
0614: control,
0615: Messages.FeatureTableControl_noEditor1
0616: + concreteType.getSimpleName()
0617: + Messages.FeatureTableControl_noEditor2);
0618: editors[i + 1] = textCellEditor;
0619: }
0620: }
0621: tableViewer.setCellEditors(editors);
0622: }
0623:
0624: private void createAttributeColumns(final Table table,
0625: TableViewer viewer, TableLayout layout) {
0626:
0627: if (features == null) {
0628: TableColumn column = new TableColumn(table, SWT.CENTER
0629: | SWT.BORDER);
0630: column.setText(Messages.FeatureTableControl_1);
0631: layout.addColumnData(new ColumnWeightData(1));
0632: } else {
0633:
0634: FeatureType schema = features.getSchema();
0635:
0636: TableColumn column = new TableColumn(table, SWT.CENTER
0637: | SWT.BORDER);
0638: column.setText("FID"); //$NON-NLS-1$
0639: layout.addColumnData(new ColumnWeightData(1, 150, true));
0640: column.setMoveable(true);
0641:
0642: column.addListener(SWT.Selection,
0643: new AttributeColumnSortListener(this ,
0644: FEATURE_ID_COLUMN_PROPERTY));
0645:
0646: for (int i = 0; i < schema.getAttributeCount(); i++) {
0647: AttributeType aType = schema.getAttributeType(i);
0648: column = new TableColumn(table, SWT.CENTER | SWT.BORDER);
0649: if (Geometry.class.isAssignableFrom(aType.getType())) { // was aType.isGeometry()
0650: // jg: wot is this maddness? jd: paul said so
0651: column.setText("GEOMETRY"); //$NON-NLS-1$
0652: } else
0653: column.setText(aType.getName());
0654:
0655: layout
0656: .addColumnData(new ColumnWeightData(1, 100,
0657: true));
0658: column.setMoveable(true);
0659:
0660: column.addListener(SWT.Selection,
0661: new AttributeColumnSortListener(this , aType
0662: .getName()));
0663: }
0664:
0665: }
0666: }
0667:
0668: /**
0669: * Does nothing.
0670: *
0671: * @see org.eclipse.ui.IWorkbenchPart#setFocus()
0672: */
0673: public void setFocus() {
0674: // do nothing.
0675: }
0676:
0677: /**
0678: * Contents of the current page of features
0679: *
0680: * @return
0681: */
0682: public FeatureCollection getFeatures() {
0683: return features;
0684: }
0685:
0686: /** Set up for a single page of content */
0687: public void setFeatures(FeatureCollection features) {
0688: checkWidget();
0689: if (this .features != null && this .features == features)
0690: return;
0691:
0692: this .features = features;
0693:
0694: createTableViewer(book);
0695: }
0696:
0697: private void checkWidget() {
0698: if (Display.getCurrent() == null)
0699: SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
0700: }
0701:
0702: /**
0703: * Don't display nothing :-)
0704: */
0705: public void clear() {
0706: features = null;
0707: selectionProvider.getSelectionFids().clear();
0708: update();
0709: }
0710:
0711: /**
0712: * Displays a message. If text == null or "" then the message is hidden and tableViewer is shown again.
0713: *
0714: * @param text message to display
0715: * @param background color of the background of the text widget. If null the default color is used
0716: * @param foreground color of the foreground of the text widget. If null the default color is used
0717: */
0718: public void message(String text, Color background, Color foreground) {
0719: checkWidget();
0720: Color background2 = background;
0721: Color foreground2 = foreground;
0722: if (background2 == null) {
0723: background2 = messageBackground;
0724: }
0725: if (foreground2 == null) {
0726: foreground2 = messageForeground;
0727: }
0728: message.setBackground(background2);
0729: message.setForeground(foreground2);
0730: if (text == null || text.trim().length() == 0) {
0731: message.setText(""); //$NON-NLS-1$
0732: if (tableViewer != null) {
0733: book.showPage(tableViewer.getControl());
0734: UiPlugin
0735: .trace(
0736: Trace.FEATURE_TABLE,
0737: getClass(),
0738: "message(String,Color,Color): showing table View", SHOW_PATH ? new Exception() : null); //$NON-NLS-1$
0739: }
0740: } else {
0741: message.setText(text);
0742: book.showPage(message);
0743: UiPlugin
0744: .trace(
0745: Trace.FEATURE_TABLE,
0746: getClass(),
0747: "message(String,Color,Color): showing message", SHOW_PATH ? new Exception() : null); //$NON-NLS-1$
0748: }
0749: }
0750:
0751: /**
0752: * Displays a message. If text == null or "" then the message is hidden and tableViewer is shown again.
0753: *
0754: * @param text message to display
0755: */
0756: public void message(String text) {
0757: message(text, null, null);
0758: }
0759:
0760: /**
0761: * Returns a selection with a single FidFilter indicating the features selected
0762: */
0763: public ISelection getSelection() {
0764: checkWidget();
0765: return selectionProvider.getSelection();
0766: }
0767:
0768: public void addSelectionChangedListener(
0769: ISelectionChangedListener listener) {
0770: selectionProvider.addSelectionChangedListener(listener);
0771: }
0772:
0773: public void removeSelectionChangedListener(
0774: ISelectionChangedListener listener) {
0775: selectionProvider.removeSelectionChangedListener(listener);
0776: }
0777:
0778: /**
0779: * Useable selections are:
0780: * selection of features, FIDS and Filters/Queries that adapt to a FeatureSource
0781: */
0782: public void setSelection(final ISelection newSelection) {
0783: checkWidget();
0784: selectionProvider.setSelection(newSelection);
0785: }
0786:
0787: /**
0788: * Sorts the table so that the selection is at the top of the table.
0789: *
0790: * It does not last. The next selection will not be at the top.
0791: */
0792: public void promoteSelection() {
0793: checkWidget();
0794: tableViewer.cancelEditing();
0795:
0796: Table table = tableViewer.getTable();
0797: table.setSortColumn(null);
0798:
0799: FidFilter filter = selectionProvider.getFidFilter();
0800:
0801: sort(new SelectionComparator(filter, SWT.UP, new FIDComparator(
0802: SWT.DOWN)), SWT.UP, null);
0803: table.setTopIndex(0);
0804: }
0805:
0806: /**
0807: * Sorts the features in the tableView.
0808: *
0809: * @param comparator comparator to use for the sorting.
0810: * @param dir the direction to set the column SWT.UP or SWT.DOWN.
0811: * If SWT.UP then the table item with index 0 is at the top of the table otherwise
0812: * it is at the bottom of the table.
0813: * @param sortColumn the column that is being sorted
0814: */
0815: public void sort(Comparator<Feature> comparator, int dir,
0816: TableColumn sortColumn) {
0817: checkWidget();
0818:
0819: FeatureTableContentProvider provider = (FeatureTableContentProvider) tableViewer
0820: .getContentProvider();
0821:
0822: boolean sorted = false;
0823: if (!comparator.equals(currentComparator)) {
0824: sorted = true;
0825: currentComparator = comparator;
0826: Collections.sort(provider.features, currentComparator);
0827: }
0828: Table table = tableViewer.getTable();
0829: if (table.getSortColumn() != sortColumn) {
0830: sorted = true;
0831: table.setSortColumn(sortColumn);
0832: while (Display.getCurrent().readAndDispatch())
0833: ;
0834: }
0835: if (table.getSortColumn() != null
0836: && dir != table.getSortDirection()) {
0837: sorted = true;
0838: table.setSortDirection(dir);
0839: while (Display.getCurrent().readAndDispatch())
0840: ;
0841: }
0842: if (sorted) {
0843: table.deselectAll();
0844: table.clearAll();
0845: }
0846:
0847: }
0848:
0849: /**
0850: * Resorts the table using the last comparator. This is useful for cases where features have been added to the table
0851: * @param refreshTable
0852: */
0853: void sort(boolean refreshTable) {
0854: if (currentComparator == null)
0855: return;
0856:
0857: FeatureTableContentProvider provider = (FeatureTableContentProvider) tableViewer
0858: .getContentProvider();
0859:
0860: Collections.sort(provider.features, currentComparator);
0861:
0862: tableViewer.getTable().deselectAll();
0863: if (refreshTable)
0864: tableViewer.getTable().clearAll();
0865: }
0866:
0867: public TableViewer getViewer() {
0868: return tableViewer;
0869: }
0870:
0871: FeatureTableSelectionProvider getSelectionProvider() {
0872: return selectionProvider;
0873: }
0874:
0875: public void setSelection(StructuredSelection selection,
0876: boolean reveal) {
0877: selectionProvider.setSelection(selection, reveal);
0878: }
0879:
0880: public int getSelectionCount() {
0881: return selectionProvider.getSelectionFids().size();
0882: }
0883:
0884: /**
0885: * select the features found that has the text. Only the attributes indicated are searched.
0886: * If {@link #ALL} is selected then all attributes will be searched
0887: *
0888: * @param text text to search for it will first be assumed that it is a reg ex expression
0889: * @param attributes the attributes to search. See {@link #ALL}
0890: * @param selectAll if true all matched features will be selected otherwise just the first feature
0891: */
0892: public void select(String text, String[] attributes,
0893: boolean selectAll) throws PatternSyntaxException {
0894:
0895: Pattern pattern;
0896: try {
0897: String pre = text.startsWith("^") ? "" : ".*"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
0898: String post = text.startsWith("$") ? "" : ".*"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
0899: pattern = Pattern.compile(pre + text + post,
0900: Pattern.CASE_INSENSITIVE);
0901: } catch (IllegalArgumentException e) {
0902: try {
0903: pattern = Pattern
0904: .compile(".*" + convertToLiteral(text) + ".*"); //$NON-NLS-1$//$NON-NLS-2$
0905: } catch (IllegalArgumentException e2) {
0906: return;
0907: }
0908: }
0909:
0910: FeatureTableContentProvider provider = (FeatureTableContentProvider) this .tableViewer
0911: .getContentProvider();
0912: List<Feature> toSearch = provider.features;
0913:
0914: IProgressMonitor progressMonitor = getSelectionProvider().progressMonitor;
0915: if (progressMonitor != null) {
0916: progressMonitor.setCanceled(true);
0917: }
0918: getSelectionProvider().getSelectionFids().clear();
0919: int j = 0;
0920: int firstMatch = -1;
0921: OUTER: for (Feature feature : toSearch) {
0922: if (searchFeature(feature, pattern, attributes)) {
0923: if (firstMatch == -1)
0924: firstMatch = j;
0925: if (!selectAll)
0926: break OUTER;
0927: }
0928: j++;
0929: }
0930:
0931: Table table = tableViewer.getTable();
0932: if (firstMatch != -1) {
0933: // display the selected item
0934: table.setTopIndex(firstMatch);
0935: }
0936: // trigger a refresh of table
0937: table.clearAll();
0938: // tell the world..
0939: selectionProvider.notifyListeners();
0940: }
0941:
0942: private boolean searchFeature(Feature feature, Pattern pattern,
0943: String[] attributes) {
0944: FeatureType featureType = feature.getFeatureType();
0945: if (attributes == ALL) {
0946: for (int i = 0; i < featureType.getAttributeCount(); i++) {
0947: if (matches(pattern, feature.getAttribute(i))) {
0948: selectionProvider.getSelectionFids().add(
0949: feature.getID());
0950: return true;
0951: }
0952: }
0953: }
0954: for (int i = 0; i < attributes.length; i++) {
0955: if (matches(pattern, feature.getAttribute(attributes[i]))) {
0956: getSelectionProvider().getSelectionFids().add(
0957: feature.getID());
0958: return true;
0959:
0960: }
0961: }
0962: return false;
0963: }
0964:
0965: private String convertToLiteral(String text) {
0966: String text2 = text.replace("\\", "\\\\"); //$NON-NLS-1$ //$NON-NLS-2$
0967: text2 = text2.replace("*", "\\*"); //$NON-NLS-1$ //$NON-NLS-2$
0968: text2 = text2.replace("+", "\\+"); //$NON-NLS-1$ //$NON-NLS-2$
0969: text2 = text2.replace(".", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
0970: text2 = text2.replace("?", "\\?"); //$NON-NLS-1$ //$NON-NLS-2$
0971: text2 = text2.replace("[", "\\["); //$NON-NLS-1$ //$NON-NLS-2$
0972: text2 = text2.replace("]", "\\]"); //$NON-NLS-1$ //$NON-NLS-2$
0973: text2 = text2.replace("^", "\\^"); //$NON-NLS-1$ //$NON-NLS-2$
0974: text2 = text2.replace("-", "\\-"); //$NON-NLS-1$ //$NON-NLS-2$
0975: text2 = text2.replace("&", "\\&"); //$NON-NLS-1$ //$NON-NLS-2$
0976: text2 = text2.replace("(", "\\("); //$NON-NLS-1$ //$NON-NLS-2$
0977: text2 = text2.replace(")", "\\)"); //$NON-NLS-1$ //$NON-NLS-2$
0978: text2 = text2.replace("|", "\\|"); //$NON-NLS-1$ //$NON-NLS-2$
0979: return text2;
0980: }
0981:
0982: private boolean matches(Pattern pattern, Object attribute) {
0983: String stringValue = attribute.toString();
0984: return pattern.matcher(stringValue).matches();
0985: }
0986:
0987: public void addLoadingListener(IFeatureTableLoadingListener listener) {
0988: loadingListeners.add(listener);
0989: }
0990:
0991: public void remove(IFeatureTableLoadingListener listener) {
0992: loadingListeners.remove(listener);
0993: }
0994:
0995: protected void notifyLoadingListeners(LoadingEvent event) {
0996: this .checkWidget();
0997: if (event.loading) {
0998: if (event.monitor == null)
0999: throw new NullPointerException();
1000: for (IFeatureTableLoadingListener listener : loadingListeners) {
1001: try {
1002: listener.loadingStarted(event.monitor);
1003: } catch (Throwable e) {
1004: UiPlugin.log(listener + " threw an exception", e); //$NON-NLS-1$
1005: }
1006: }
1007: } else {
1008: for (IFeatureTableLoadingListener listener : loadingListeners) {
1009: try {
1010: listener.loadingStopped(event.canceled);
1011: } catch (Throwable e) {
1012: UiPlugin.log(listener + " threw an exception", e); //$NON-NLS-1$
1013: }
1014: }
1015: }
1016: }
1017:
1018: /**
1019: * Updates the features that have the same feature ID to match the new feature or adds the features if they are not part of the
1020: * current collection.
1021: *
1022: * @param features2 the feature collection that contains the modified or new features.
1023: */
1024: public void update(FeatureCollection features2) {
1025: if (features == null)
1026: return; // nothing to update since the table is not in use... Should this be an exception?
1027: FeatureTableContentProvider provider = (FeatureTableContentProvider) tableViewer
1028: .getContentProvider();
1029: provider.update(features2);
1030: }
1031:
1032: /**
1033: * Checks all the lists, caches, content providers, etc... are consistent with each other.
1034: * This is an expensive method so should be called with care. A test is a good example.
1035: */
1036: public void assertInternallyConsistent() {
1037: if (tableViewer.getContentProvider() != null) {
1038: FeatureTableContentProvider provider = (FeatureTableContentProvider) tableViewer
1039: .getContentProvider();
1040: provider.assertInternallyConsistent();
1041: }
1042: }
1043:
1044: /**
1045: * Removes the selected features (the features selected by the owning {@link FeatureTableControl}).
1046: * @return returns a collection of the deleted features
1047: *
1048: * @see #setSelection(ISelection)
1049: * @see #setSelection(StructuredSelection, boolean)
1050: */
1051: public FeatureCollection deleteSelection() {
1052: FeatureTableContentProvider provider = (FeatureTableContentProvider) tableViewer
1053: .getContentProvider();
1054: return provider.deleteSelection();
1055: }
1056:
1057: /**
1058: * Sets the context Menu used by the table view. Not menu is used for the message box.
1059: *
1060: * @param contextMenu menu manager used for creating the menu.
1061: */
1062: public void setMenuManager(MenuManager contextMenu) {
1063: checkWidget();
1064: this .contextMenu = contextMenu;
1065: if (tableViewer != null && tableViewer.getControl() != null) {
1066: Menu oldMenu = tableViewer.getControl().getMenu();
1067: if (oldMenu != null)
1068: oldMenu.dispose();
1069: Menu menu = contextMenu.createContextMenu(tableViewer
1070: .getControl());
1071: tableViewer.getControl().setMenu(menu);
1072: }
1073: }
1074:
1075: }
|