| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2015 Ericsson |
| 3 | * |
| 4 | * All rights reserved. This program and the accompanying materials are |
| 5 | * made available under the terms of the Eclipse Public License v1.0 which |
| 6 | * accompanies this distribution, and is available at |
| 7 | * http://www.eclipse.org/legal/epl-v10.html |
| 8 | * |
| 9 | * Contributors: |
| 10 | * Alexandre Montplaisir - Initial API and implementation |
| 11 | * Matthew Khouzam - Initial API and implementation |
| 12 | *******************************************************************************/ |
| 13 | |
| 14 | package org.eclipse.tracecompass.tmf.ui.viewers.table; |
| 15 | |
| 16 | import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; |
| 17 | import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString; |
| 18 | |
| 19 | import java.util.Collections; |
| 20 | import java.util.Comparator; |
| 21 | |
| 22 | import org.eclipse.jdt.annotation.NonNull; |
| 23 | import org.eclipse.jdt.annotation.Nullable; |
| 24 | import org.eclipse.jface.action.IMenuListener; |
| 25 | import org.eclipse.jface.action.IMenuManager; |
| 26 | import org.eclipse.jface.action.MenuManager; |
| 27 | import org.eclipse.jface.viewers.ColumnLabelProvider; |
| 28 | import org.eclipse.jface.viewers.IContentProvider; |
| 29 | import org.eclipse.jface.viewers.ILabelProvider; |
| 30 | import org.eclipse.jface.viewers.ISelection; |
| 31 | import org.eclipse.jface.viewers.IStructuredSelection; |
| 32 | import org.eclipse.jface.viewers.TableViewer; |
| 33 | import org.eclipse.jface.viewers.TableViewerColumn; |
| 34 | import org.eclipse.jface.viewers.Viewer; |
| 35 | import org.eclipse.jface.viewers.ViewerCell; |
| 36 | import org.eclipse.jface.viewers.ViewerComparator; |
| 37 | import org.eclipse.jface.viewers.deferred.DeferredContentProvider; |
| 38 | import org.eclipse.swt.SWT; |
| 39 | import org.eclipse.swt.events.MouseAdapter; |
| 40 | import org.eclipse.swt.events.MouseEvent; |
| 41 | import org.eclipse.swt.events.SelectionAdapter; |
| 42 | import org.eclipse.swt.events.SelectionEvent; |
| 43 | import org.eclipse.swt.graphics.Point; |
| 44 | import org.eclipse.swt.widgets.Control; |
| 45 | import org.eclipse.swt.widgets.Menu; |
| 46 | import org.eclipse.swt.widgets.Table; |
| 47 | import org.eclipse.swt.widgets.TableColumn; |
| 48 | import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer; |
| 49 | |
| 50 | /** |
| 51 | * Generic {@link TableViewer} wrapper with most standard features enabled. |
| 52 | * <p> |
| 53 | * It provides the following features: <br> |
| 54 | * - Sortable columns <br> |
| 55 | * - Movable columns <br> |
| 56 | * - Resizable columns <br> |
| 57 | * - Tracking last clicked columns |
| 58 | * <p> |
| 59 | * The user of this class should add columns to the table by using the |
| 60 | * {@link #createColumn(String, ColumnLabelProvider, Comparator)} method, and |
| 61 | * set the content provider and input on the supplied {@link TableViewer}. |
| 62 | * |
| 63 | * @since 1.1 |
| 64 | */ |
| 65 | public class TmfSimpleTableViewer extends TmfViewer { |
| 66 | |
| 67 | /** |
| 68 | * Viewer comparator that ignores the element label strings and uses the |
| 69 | * given comparator to compare the elements directly. |
| 70 | */ |
| 71 | private static class ElementComparator<T> extends ViewerComparator { |
| 72 | |
| 73 | private Comparator<T> elementComparator; |
| 74 | |
| 75 | public ElementComparator(Comparator<T> comparator) { |
| 76 | elementComparator = comparator; |
| 77 | } |
| 78 | |
| 79 | @SuppressWarnings("unchecked") |
| 80 | @Override |
| 81 | public int compare(Viewer viewer, Object e1, Object e2) { |
| 82 | return elementComparator.compare((T) e1, (T) e2); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Comparator that compares the text of a column given by the label |
| 88 | * provider, using the text's String ordering. |
| 89 | */ |
| 90 | private static class ColumnLabelComparator implements Comparator<Object> { |
| 91 | private final ILabelProvider fLabelProvider; |
| 92 | |
| 93 | public ColumnLabelComparator(ILabelProvider labelProvider) { |
| 94 | fLabelProvider = labelProvider; |
| 95 | } |
| 96 | |
| 97 | @Override |
| 98 | public int compare(Object o1, Object o2) { |
| 99 | String s1 = nullToEmptyString(fLabelProvider.getText(o1)); |
| 100 | String s2 = nullToEmptyString(fLabelProvider.getText(o2)); |
| 101 | return s1.compareTo(s2); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | private final class MouseColumnListener extends MouseAdapter { |
| 106 | @Override |
| 107 | public void mouseDown(MouseEvent e) { |
| 108 | ViewerCell cell = fTableViewer.getCell(new Point(e.x, e.y)); |
| 109 | fSelectedColumn = (cell != null) ? cell.getColumnIndex() : -1; |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | private final class ColumnSorter<T> extends SelectionAdapter { |
| 114 | private final @NonNull TableColumn fColumn; |
| 115 | private final @NonNull Comparator<T> fComparator; |
| 116 | |
| 117 | private ColumnSorter(@NonNull TableColumn column, @NonNull Comparator<T> comparator) { |
| 118 | fColumn = column; |
| 119 | fComparator = comparator; |
| 120 | } |
| 121 | |
| 122 | @Override |
| 123 | public void widgetSelected(SelectionEvent e) { |
| 124 | Table table = fTableViewer.getTable(); |
| 125 | TableColumn prevSortcolumn = table.getSortColumn(); |
| 126 | if (prevSortcolumn == fColumn) { |
| 127 | flipSortDirection(); |
| 128 | } |
| 129 | table.setSortDirection(fDirection); |
| 130 | table.setSortColumn(fColumn); |
| 131 | Comparator<T> comparator; |
| 132 | if (fDirection == SWT.DOWN) { |
| 133 | comparator = fComparator; |
| 134 | } else { |
| 135 | comparator = checkNotNull(Collections.reverseOrder(fComparator)); |
| 136 | } |
| 137 | IContentProvider contentProvider = fTableViewer.getContentProvider(); |
| 138 | if (contentProvider instanceof DeferredContentProvider) { |
| 139 | DeferredContentProvider deferredContentProvider = (DeferredContentProvider) contentProvider; |
| 140 | deferredContentProvider.setSortOrder(comparator); |
| 141 | } else if (contentProvider instanceof ISortingLazyContentProvider) { |
| 142 | ISortingLazyContentProvider sortingLazyContentProvider = (ISortingLazyContentProvider) contentProvider; |
| 143 | sortingLazyContentProvider.setSortOrder(comparator); |
| 144 | } else { |
| 145 | fTableViewer.setComparator(new ElementComparator<>(comparator)); |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | private static final int DEFAULT_COL_WIDTH = 200; |
| 151 | private final TableViewer fTableViewer; |
| 152 | |
| 153 | private int fDirection; |
| 154 | private int fSelectedColumn; |
| 155 | |
| 156 | private MenuManager fTablePopupMenuManager; |
| 157 | |
| 158 | /** |
| 159 | * Constructor that initializes the parent of the viewer |
| 160 | * |
| 161 | * @param table |
| 162 | * the {@link TableViewer} to wrap |
| 163 | */ |
| 164 | public TmfSimpleTableViewer(TableViewer table) { |
| 165 | super(table.getControl().getParent()); |
| 166 | fTableViewer = table; |
| 167 | |
| 168 | final Table tableControl = fTableViewer.getTable(); |
| 169 | tableControl.setHeaderVisible(true); |
| 170 | tableControl.setLinesVisible(true); |
| 171 | |
| 172 | fDirection = SWT.DOWN; |
| 173 | fTableViewer.setUseHashlookup(true); |
| 174 | fTableViewer.getControl().addMouseListener(new MouseColumnListener()); |
| 175 | |
| 176 | fTablePopupMenuManager = new MenuManager(); |
| 177 | fTablePopupMenuManager.setRemoveAllWhenShown(true); |
| 178 | |
| 179 | fTablePopupMenuManager.addMenuListener(new IMenuListener() { |
| 180 | @Override |
| 181 | public void menuAboutToShow(final @Nullable IMenuManager manager) { |
| 182 | TableViewer viewer = getTableViewer(); |
| 183 | ISelection selection = viewer.getSelection(); |
| 184 | if (selection instanceof IStructuredSelection) { |
| 185 | IStructuredSelection sel = (IStructuredSelection) selection; |
| 186 | if (manager != null) { |
| 187 | appendToTablePopupMenu(manager, sel); |
| 188 | } |
| 189 | } |
| 190 | } |
| 191 | }); |
| 192 | |
| 193 | Menu tablePopup = fTablePopupMenuManager.createContextMenu(getTableViewer().getTable()); |
| 194 | getTableViewer().getTable().setMenu(tablePopup); |
| 195 | } |
| 196 | |
| 197 | @Override |
| 198 | public void dispose() { |
| 199 | if (fTableViewer != null) { |
| 200 | fTableViewer.getControl().dispose(); |
| 201 | } |
| 202 | if (fTablePopupMenuManager != null) { |
| 203 | fTablePopupMenuManager.dispose(); |
| 204 | } |
| 205 | super.dispose(); |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Method to add commands to the context sensitive menu. |
| 210 | * @param manager |
| 211 | * the menu manager |
| 212 | * @param sel |
| 213 | * the current selection |
| 214 | * @since 2.0 |
| 215 | */ |
| 216 | protected void appendToTablePopupMenu(@NonNull IMenuManager manager, @NonNull IStructuredSelection sel) { |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Create a column for the table. The column will have a default width set, |
| 221 | * and will be resizable, moveable and sortable. |
| 222 | * |
| 223 | * @param name |
| 224 | * the name of the column |
| 225 | * @param provider |
| 226 | * the label provider of the column |
| 227 | * @param comparator |
| 228 | * the comparator associated with clicking on the column, if it |
| 229 | * is null, a string comparator on the label will be used |
| 230 | * @return the column that was created |
| 231 | * @since 2.0 |
| 232 | */ |
| 233 | public final <T> TableColumn createColumn(String name, ColumnLabelProvider provider, @Nullable Comparator<T> comparator) { |
| 234 | TableViewerColumn col = new TableViewerColumn(fTableViewer, SWT.NONE); |
| 235 | col.setLabelProvider(provider); |
| 236 | final TableColumn column = col.getColumn(); |
| 237 | column.setWidth(DEFAULT_COL_WIDTH); |
| 238 | column.setText(name); |
| 239 | column.setResizable(true); |
| 240 | column.setMoveable(true); |
| 241 | if (comparator == null) { |
| 242 | column.addSelectionListener(new ColumnSorter<>(column, new ColumnLabelComparator(provider))); |
| 243 | } else { |
| 244 | column.addSelectionListener(new ColumnSorter<>(column, comparator)); |
| 245 | } |
| 246 | return column; |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Reverse the sort direction |
| 251 | */ |
| 252 | private void flipSortDirection() { |
| 253 | if (fDirection == SWT.DOWN) { |
| 254 | fDirection = SWT.UP; |
| 255 | } else { |
| 256 | fDirection = SWT.DOWN; |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | @Override |
| 261 | public final Control getControl() { |
| 262 | return fTableViewer.getControl(); |
| 263 | } |
| 264 | |
| 265 | /** |
| 266 | * Gets the wrapped table viewer |
| 267 | * |
| 268 | * @return the table viewer |
| 269 | */ |
| 270 | public final TableViewer getTableViewer() { |
| 271 | return fTableViewer; |
| 272 | } |
| 273 | |
| 274 | /** |
| 275 | * Get the selected column index |
| 276 | * |
| 277 | * @return the selected column index or -1 |
| 278 | */ |
| 279 | public final int getColumnIndex() { |
| 280 | return fSelectedColumn; |
| 281 | } |
| 282 | |
| 283 | @Override |
| 284 | public final void refresh() { |
| 285 | fTableViewer.refresh(); |
| 286 | } |
| 287 | } |