Bug 437372 - [model editor] [list tab] add ability to sort by clicking
on column

Change-Id: I748520f0e8ebf4af5c8db2ecdd5dc3ccc81f8255
Signed-off-by: Steven Spungin <steven@spungin.tv>
diff --git a/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/tabs/ListTab.java b/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/tabs/ListTab.java
index f17fa63..667cc81 100644
--- a/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/tabs/ListTab.java
+++ b/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/tabs/ListTab.java
@@ -6,7 +6,7 @@
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
- *     Steven Spungin <steven@spungin.tv> - initial API and implementation, Bug 432555, Bug 436889
+ *     Steven Spungin <steven@spungin.tv> - initial API and implementation, Bug 432555, Bug 436889, Bug 437372
  *******************************************************************************/
 
 package org.eclipse.e4.tools.emf.ui.internal.common.component.tabs;
@@ -14,7 +14,6 @@
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -45,6 +44,7 @@
 import org.eclipse.e4.tools.emf.ui.internal.ResourceProvider;
 import org.eclipse.e4.tools.emf.ui.internal.common.ModelEditor;
 import org.eclipse.e4.tools.emf.ui.internal.common.component.dialogs.BundleImageCache;
+import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.EAttributeEditingSupport.ATT_TYPE;
 import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.E;
 import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.EmptyFilterOption;
 import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.TitleAreaFilterDialog;
@@ -259,7 +259,7 @@
 					}
 
 					// move it to the end of the list.
-					int currentIndex = getVisibleColumnIndex(tvResults, col);
+					int currentIndex = TableViewerUtil.getVisibleColumnIndex(tvResults, col);
 					int[] order = tvResults.getTable().getColumnOrder();
 					for (int idx = 0; idx < order.length; idx++) {
 						if (order[idx] > currentIndex) {
@@ -308,16 +308,6 @@
 	}
 
 	// @Refactor
-	static public int getVisibleColumnIndex(TableViewer tvResults2, TableColumn col) {
-		int createOrder = Arrays.asList(tvResults2.getTable().getColumns()).indexOf(col);
-		if (createOrder == -1) {
-			return -1;
-		} else {
-			return tvResults2.getTable().getColumnOrder()[createOrder];
-		}
-	}
-
-	// @Refactor
 	static private String docToString(Document doc) throws TransformerException {
 		TransformerFactory tf = TransformerFactory.newInstance();
 		Transformer transformer = tf.newTransformer();
@@ -585,11 +575,11 @@
 
 		app.getContext().set("org.eclipse.e4.tools.active-object-viewer", this); //$NON-NLS-1$
 
-		EAttributeTableViewerColumn colId = new EAttributeTableViewerColumn(tvResults, "elementId", "elementId", context);
-		defaultColumns.put("elementId", colId); //$NON-NLS-1$
+		EAttributeTableViewerColumn colId = new EAttributeTableViewerColumn(tvResults, "elementId", "elementId", context); //$NON-NLS-2$
+		defaultColumns.put("elementId", colId);
 
-		EAttributeTableViewerColumn colLabel = new EAttributeTableViewerColumn_Markable(tvResults, "label", "label", context); //$NON-NLS-1$ //$NON-NLS-2$
-		defaultColumns.put("label", colLabel); //$NON-NLS-1$
+		EAttributeTableViewerColumn colLabel = new EAttributeTableViewerColumn_Markable(tvResults, "label", "label", context); //$NON-NLS-2$
+		defaultColumns.put("label", colLabel);
 
 		// Custom selection for marked items
 		tvResults.getTable().addListener(SWT.EraseItem, new Listener() {
@@ -621,18 +611,41 @@
 
 		tvResults.getTable().setFocus();
 
-		for (EAttributeTableViewerColumn col : defaultColumns.values()) {
+		for (final EAttributeTableViewerColumn col : defaultColumns.values()) {
 			col.getTableViewerColumn().getColumn().setMoveable(true);
 		}
 		for (TableColumn col : requiredColumns.values()) {
 			col.setMoveable(true);
 		}
 
+		makeSortable(colId.getTableViewerColumn().getColumn(), new AttributeColumnLabelSorter(colId.getTableViewerColumn().getColumn(), "elementId")); //$NON-NLS-1$
+		makeSortable(colLabel.getTableViewerColumn().getColumn(), new AttributeColumnLabelSorter(colLabel.getTableViewerColumn().getColumn(), "label")); //$NON-NLS-1$
+		makeSortable(colItem.getColumn(), new TableViewerUtil.ColumnLabelSorter(colItem.getColumn()));
+		makeSortable(colMarked.getColumn(), new TableViewerUtil.AbstractInvertableTableSorter() {
+
+			@Override
+			public int compare(Viewer viewer, Object e1, Object e2) {
+				boolean mark1 = isHighlighted(e1);
+				boolean mark2 = isHighlighted(e2);
+				if (mark1 && !mark2) {
+					return -1;
+				} else if (mark2 && !mark1) {
+					return 1;
+				} else {
+					return 0;
+				}
+			}
+		});
+
 		reload();
 		TableViewerUtil.refreshAndPack(tvResults);
 		loadSettings();
 	}
 
+	private void makeSortable(TableColumn column, TableViewerUtil.AbstractInvertableTableSorter sorter) {
+		new TableViewerUtil.TableSortSelectionListener(tvResults, column, sorter, SWT.UP, false);
+	}
+
 	public void reload() {
 		tvResults.setInput(modelResource);
 	}
@@ -775,12 +788,43 @@
 				colName = new EAttributeTableViewerColumn_Markable(tvResults, attName, attName, context);
 				optionalColumns.put(attName, colName);
 				colName.getTableViewerColumn().getColumn().setMoveable(true);
+				makeSortable(colName.getTableViewerColumn().getColumn(), new AttributeColumnLabelSorter(colName.getTableViewerColumn().getColumn(), attName));
 				tvResults.refresh();
 			}
 		}
 		return colName;
 	}
 
+	static private class AttributeColumnLabelSorter extends TableViewerUtil.ColumnLabelSorter {
+
+		private String attName;
+
+		AttributeColumnLabelSorter(TableColumn col, String attName) {
+			super(col);
+			this.attName = attName;
+		}
+
+		@Override
+		public int compare(Viewer viewer, Object e1, Object e2) {
+			// if either is boolean, use boolean value, otherwise use text value
+			ATT_TYPE e1Type = EAttributeEditingSupport.getAttributeType(e1, attName);
+			ATT_TYPE e2Type = EAttributeEditingSupport.getAttributeType(e2, attName);
+			if (e1Type == ATT_TYPE.BOOLEAN || e2Type == ATT_TYPE.BOOLEAN) {
+				Boolean b1 = (Boolean) (EmfUtil.getAttributeValue((EObject) e1, attName));
+				Boolean b2 = (Boolean) (EmfUtil.getAttributeValue((EObject) e2, attName));
+				if (b1 == null && b2 != null) {
+					return -2;
+				} else if (b2 == null && b1 != null) {
+					return 2;
+				} else {
+					return (b1.compareTo(b2));
+				}
+			} else {
+				return super.compare(viewer, e1, e2);
+			}
+		}
+	}
+
 	private class EAttributeTableViewerColumn_Markable extends EAttributeTableViewerColumn {
 		public EAttributeTableViewerColumn_Markable(TableViewer tvResults, String label, String attName, IEclipseContext context) {
 			super(tvResults, label, attName, context);
diff --git a/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/tabs/TableViewerUtil.java b/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/tabs/TableViewerUtil.java
index c182232..728cf3f 100644
--- a/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/tabs/TableViewerUtil.java
+++ b/bundles/org.eclipse.e4.tools.emf.ui/src/org/eclipse/e4/tools/emf/ui/internal/common/component/tabs/TableViewerUtil.java
@@ -6,24 +6,153 @@
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
- *     Steven Spungin <steven@spungin.tv> - initial API and implementation
+ *     Steven Spungin <steven@spungin.tv> - initial API and implementation, ongoing maintenance
  *******************************************************************************/
 
 package org.eclipse.e4.tools.emf.ui.internal.common.component.tabs;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
-
-import java.util.Arrays;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
 import org.eclipse.jface.viewers.TableViewer;
 import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.jface.viewers.ViewerSorter;
+import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.widgets.TableColumn;
 
 public class TableViewerUtil {
+
+	// Sorts the column by the provider's text value
+	static class ColumnLabelSorter extends TableViewerUtil.AbstractInvertableTableSorter {
+		private final TableColumn col;
+
+		ColumnLabelSorter(TableColumn col) {
+			this.col = col;
+		}
+
+		@Override
+		public int compare(Viewer viewer, Object e1, Object e2) {
+			TableViewer tableViewer = (TableViewer) viewer;
+			ColumnLabelProvider labelProvider = (ColumnLabelProvider) tableViewer.getLabelProvider(Arrays.asList(tableViewer.getTable().getColumns()).indexOf(col));
+			return labelProvider.getText(e1).compareTo(labelProvider.getText(e2));
+		}
+	}
+
+	static abstract class InvertableSorter extends ViewerSorter {
+		@Override
+		public abstract int compare(Viewer viewer, Object e1, Object e2);
+
+		abstract InvertableSorter getInverseSorter();
+
+		public abstract int getSortDirection();
+	}
+
+	static public abstract class AbstractInvertableTableSorter extends InvertableSorter {
+		private final InvertableSorter inverse = new InvertableSorter() {
+
+			@Override
+			public int compare(Viewer viewer, Object e1, Object e2) {
+				return (-1) * AbstractInvertableTableSorter.this.compare(viewer, e1, e2);
+			}
+
+			@Override
+			InvertableSorter getInverseSorter() {
+				return AbstractInvertableTableSorter.this;
+			}
+
+			@Override
+			public int getSortDirection() {
+				return SWT.DOWN;
+			}
+		};
+
+		@Override
+		InvertableSorter getInverseSorter() {
+			return inverse;
+		}
+
+		@Override
+		public int getSortDirection() {
+			return SWT.UP;
+		}
+	}
+
+	static public class TableSortSelectionListener implements SelectionListener {
+		private final TableViewer viewer;
+		private final TableColumn column;
+		private final InvertableSorter sorter;
+		private final boolean keepDirection;
+		private InvertableSorter currentSorter;
+
+		/**
+		 * The constructor of this listener.
+		 *
+		 * @param viewer
+		 *            the tableviewer this listener belongs to
+		 * @param column
+		 *            the column this listener is responsible for
+		 * @param sorter
+		 *            the sorter this listener uses
+		 * @param defaultDirection
+		 *            the default sorting direction of this Listener. Possible
+		 *            values are {@link SWT.UP} and {@link SWT.DOWN}
+		 * @param keepDirection
+		 *            if true, the listener will remember the last sorting
+		 *            direction of the associated column and restore it when the
+		 *            column is reselected. If false, the listener will use the
+		 *            default sorting direction
+		 */
+		public TableSortSelectionListener(TableViewer viewer, TableColumn column, AbstractInvertableTableSorter sorter, int defaultDirection, boolean keepDirection) {
+			this.viewer = viewer;
+			this.column = column;
+			this.keepDirection = keepDirection;
+			this.sorter = (defaultDirection == SWT.UP) ? sorter : sorter.getInverseSorter();
+			this.currentSorter = this.sorter;
+
+			this.column.addSelectionListener(this);
+		}
+
+		/**
+		 * Chooses the column of this listener for sorting of the table. Mainly
+		 * used when first initializing the table.
+		 */
+		public void chooseColumnForSorting() {
+			viewer.getTable().setSortColumn(column);
+			viewer.getTable().setSortDirection(currentSorter.getSortDirection());
+			viewer.setSorter(currentSorter);
+		}
+
+		@Override
+		public void widgetSelected(SelectionEvent e) {
+			InvertableSorter newSorter;
+			if (viewer.getTable().getSortColumn() == column) {
+				newSorter = ((InvertableSorter) viewer.getSorter()).getInverseSorter();
+			} else {
+				if (keepDirection) {
+					newSorter = currentSorter;
+				} else {
+					newSorter = sorter;
+				}
+			}
+
+			currentSorter = newSorter;
+			chooseColumnForSorting();
+		}
+
+		@Override
+		public void widgetDefaultSelected(SelectionEvent e) {
+			widgetSelected(e);
+		}
+	}
+
 	static public void refreshAndPack(TableViewer viewer) {
 		viewer.refresh();
 		packAllColumns(viewer);
@@ -77,7 +206,7 @@
 		final ArrayList<TableColumn> allCols = new ArrayList<TableColumn>(Arrays.asList(viewer.getTable().getColumns()));
 		final int[] order = viewer.getTable().getColumnOrder();
 		Collections.sort(allCols, new Comparator<TableColumn>() {
-	
+
 			@Override
 			public int compare(TableColumn o1, TableColumn o2) {
 				return order[allCols.indexOf(o1)] - order[allCols.indexOf(o2)];
@@ -85,4 +214,13 @@
 		});
 		return allCols;
 	}
+
+	static public int getVisibleColumnIndex(TableViewer tvResults2, TableColumn col) {
+		int createOrder = Arrays.asList(tvResults2.getTable().getColumns()).indexOf(col);
+		if (createOrder == -1) {
+			return -1;
+		} else {
+			return tvResults2.getTable().getColumnOrder()[createOrder];
+		}
+	}
 }
\ No newline at end of file