package edu.ualberta.med.biobank.gui.common.widgets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.TableLayout; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Text; import edu.ualberta.med.biobank.common.util.DelegatingList; import edu.ualberta.med.biobank.common.util.ListChangeHandler; import edu.ualberta.med.biobank.common.util.ListChangeSource; import edu.ualberta.med.biobank.gui.common.BgcPlugin; import edu.ualberta.med.biobank.gui.common.widgets.utils.BgcClipboard; import gov.nih.nci.system.applicationservice.ApplicationException; /** * This abstract class is used to create most the tables used in the client. The * template parameter is the collection that contains the information to be * displayed in the table. * <p> * Derived classes must provide the following (through the use of abstract * methods): * <ul> * <li>label provider</li> * <li>table sorter</li> * </ul> * * This class contains the following features: * <ul> * <li>column sorting</li> * <li>copy row to clipboard</li> * <li>pagination widget</li> * </ul> */ public abstract class AbstractInfoTableWidget<T> extends BgcBaseWidget implements IInfoTablePagination, ListChangeSource<T> { public static class RowItem { int itemNumber; } protected final DelegatingList<T> list = new DelegatingList<T>(); protected TableViewer tableViewer; protected Menu menu; protected boolean paginationRequired; protected int start; protected int end; protected boolean reloadData = false; protected boolean autoSizeColumns; private BgcTableSorter tableSorter; private BgcLabelProvider labelProvider; protected PaginationWidget paginationWidget; // TODO: all listeners can be managed by an external class protected ListenerList addItemListeners = new ListenerList(); protected ListenerList editItemListeners = new ListenerList(); protected ListenerList deleteItemListeners = new ListenerList(); protected ListenerList doubleClickListeners = new ListenerList(); List<MenuItem> items = new ArrayList<MenuItem>(); public AbstractInfoTableWidget(final Composite parent, final String[] headings, int[] columnWidths, int rowsPerPage) { super(parent, SWT.NONE); GridLayout gl = new GridLayout(1, false); gl.verticalSpacing = 1; setLayout(gl); setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); int style = SWT.BORDER | SWT.FULL_SELECTION | SWT.VIRTUAL; tableViewer = new TableViewer(this, style); tableSorter = getTableSorter(); labelProvider = getLabelProvider(); Table table = tableViewer.getTable(); table.setLayout(new TableLayout()); table.setHeaderVisible(true); table.setLinesVisible(true); GridData gd = new GridData(SWT.FILL, SWT.NONE, true, true); table.setLayoutData(gd); tableViewer.setSorter(tableSorter); tableViewer.addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { AbstractInfoTableWidget.this.doubleClick(); } }); setHeadings(headings, columnWidths); tableViewer.setUseHashlookup(true); tableViewer.setLabelProvider(labelProvider); tableViewer.setContentProvider(new ArrayContentProvider()); paginationWidget = new PaginationWidget(this, SWT.NONE, this, PaginationWidget.NEXT_PAGE_BUTTON | PaginationWidget.LAST_PAGE_BUTTON, rowsPerPage); autoSizeColumns = (columnWidths == null) ? true : false; menu = new Menu(parent); tableViewer.getTable().setMenu(menu); // This code serves to provide menus on a selection basis, with // permission checking menu.addListener(SWT.Show, new Listener() { @Override public void handleEvent(Event event) { try { for (MenuItem item : items) item.dispose(); items.clear(); if (addItemListeners.getListeners().length > 0) { MenuItem item = new MenuItem(menu, SWT.PUSH); item.setText(Messages.AbstractInfoTableWidget_add); item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { addItem(); } }); items.add(item); } if (editItemListeners.getListeners().length > 0 && canEdit(getSelection())) { MenuItem item = new MenuItem(menu, SWT.PUSH); item.setText(Messages.AbstractInfoTableWidget_edit); item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { editItem(); } }); items.add(item); } if (deleteItemListeners.getListeners().length > 0 && canDelete(getSelection())) { MenuItem item = new MenuItem(menu, SWT.PUSH); item.setText(Messages.AbstractInfoTableWidget_delete); item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { deleteItem(); } }); items.add(item); } } catch (Exception e) { BgcPlugin.openAsyncError("Error", "Unable to load menu for selection", e); } BgcClipboard.addClipboardCopySupport(tableViewer, menu, labelProvider, headings.length); } }); // need to autosize at creation to be sure the size is well initialized // the first time. (if don't do that, display problems in UserManagement // Dialog): if (autoSizeColumns) { autoSizeColumns(); } } public void setHeadings(String[] headings) { setHeadings(headings, null); } public void setHeadings(String[] headings, int[] columnWidths) { int index = 0; if (headings != null) { for (String name : headings) { final TableViewerColumn viewerCol = new TableViewerColumn( tableViewer, SWT.NONE); final TableColumn col = viewerCol.getColumn(); final int fIndex = index; col.setText(name); if ((columnWidths == null) || (columnWidths[index] == -1)) { col.pack(); } else { col.setWidth(columnWidths[index]); } col.setResizable(true); col.setMoveable(true); col.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { col.pack(); Table table = tableViewer.getTable(); if (tableSorter != null) { tableSorter.setColumn(fIndex); int dir = table.getSortDirection(); if (tableViewer.getTable().getSortColumn() == col) { dir = (dir == SWT.UP) ? SWT.DOWN : SWT.UP; } else { dir = SWT.DOWN; } table.setSortDirection(dir); table.setSortColumn(col); tableViewer.refresh(); } } }); index++; } tableViewer.setColumnProperties(headings); } } protected abstract boolean isEditMode(); protected abstract BgcLabelProvider getLabelProvider(); protected abstract BgcTableSorter getTableSorter(); public abstract void reload(); @Override public boolean setFocus() { tableViewer.getControl().setFocus(); return true; } protected TableViewer getTableViewer() { return tableViewer; } protected void autoSizeColumns() { // TODO: auto-size table initially based on headers? Sort of already // done with .pack(). Table table = tableViewer.getTable(); if (table.isDisposed()) { return; } final int[] maxCellContentsWidths = new int[table.getColumnCount()]; Text textRenderer = new Text(menu.getShell(), SWT.NONE); textRenderer.setVisible(false); GridData gd = new GridData(); gd.exclude = true; textRenderer.setLayoutData(gd); for (int i = 0; i < table.getColumnCount(); i++) { textRenderer.setText(table.getColumn(i).getText()); maxCellContentsWidths[i] = textRenderer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; } for (TableItem row : table.getItems()) { for (int i = 0; i < table.getColumnCount(); i++) { String rowText = row.getText(i); Image rowImage = row.getImage(i); int cellContentsWidth = 0; if (rowText != null) { textRenderer.setText(rowText); cellContentsWidth = textRenderer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; } else if (rowImage != null) { cellContentsWidth = rowImage.getImageData().width; } maxCellContentsWidths[i] = Math.max(cellContentsWidth, maxCellContentsWidths[i]); } } textRenderer.dispose(); int sumOfMaxTextWidths = 0; for (int width : maxCellContentsWidths) { sumOfMaxTextWidths += width; } // need to give default max=500 when can't know the size of // the table // yet (see UserManagementDialog) int tableWidth = Math.max(500, tableViewer.getTable().getSize().x); int totalWidths = 0; table.setVisible(false); for (int i = 0; i < table.getColumnCount(); i++) { int width = (int) (((double) maxCellContentsWidths[i] / sumOfMaxTextWidths) * tableWidth); if (i == (table.getColumnCount() - 1)) table.getColumn(i).setWidth(tableWidth - totalWidths - 5); else table.getColumn(i).setWidth(width); totalWidths += width; } table.setVisible(true); } protected void resizeTable() { Table table = getTableViewer().getTable(); GridData gd = (GridData) table.getLayoutData(); int rows = Math.max(paginationWidget.getRowsPerPage(), 5); gd.heightHint = ((rows - 1) * table.getItemHeight()) + table.getHeaderHeight() + table.getBorderWidth(); } @Override public void setEnabled(boolean enabled) { tableViewer.getTable().setEnabled(enabled); } @Override public Menu getMenu() { return menu; } protected void showPaginationWidget() { paginationWidget.setVisible(true); } protected void enablePaginationWidget(boolean enable) { paginationWidget.setEnabled(enable); paginationWidget.enableWidgets(enable); } public void addSelectionListener(SelectionListener listener) { tableViewer.getTable().addSelectionListener(listener); } public void addClickListener(IInfoTableDoubleClickItemListener<T> listener) { doubleClickListeners.add(listener); } public void addAddItemListener(IInfoTableAddItemListener<T> listener) { addItemListeners.add(listener); } public void addEditItemListener(IInfoTableEditItemListener<T> listener) { editItemListeners.add(listener); } public void addDeleteItemListener(IInfoTableDeleteItemListener<T> listener) { deleteItemListeners.add(listener); } /** * IMPORTANT: Remember that your primary class in TableRowData must be of * type T.. can't enforce this from superclass * * @return */ public abstract T getSelection(); @SuppressWarnings("unchecked") public void doubleClick() { // get selection as derived class object Object selection = getSelection(); try { if (!canView(getSelection())) return; } catch (Exception e) { BgcPlugin.openAsyncError("Error", "Unable to open form.", e); } final InfoTableEvent<T> event = new InfoTableEvent<T>(this, new InfoTableSelection(selection)); Object[] listeners = doubleClickListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { final IInfoTableDoubleClickItemListener<T> l = (IInfoTableDoubleClickItemListener<T>) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.doubleClick(event); } }); } } protected void addItem() { Object objSelected = getSelection(); if (objSelected == null) return; InfoTableSelection selection = new InfoTableSelection(objSelected); final InfoTableEvent<T> event = new InfoTableEvent<T>(this, selection); Object[] listeners = addItemListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { @SuppressWarnings("unchecked") final IInfoTableAddItemListener<T> l = (IInfoTableAddItemListener<T>) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.addItem(event); } }); } } public void editItem() { Object objSelected = getSelection(); if (objSelected == null) return; InfoTableSelection selection = new InfoTableSelection(objSelected); final InfoTableEvent<T> event = new InfoTableEvent<T>(this, selection); Object[] listeners = editItemListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { @SuppressWarnings("unchecked") final IInfoTableEditItemListener<T> l = (IInfoTableEditItemListener<T>) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.editItem(event); } }); } } protected void deleteItem() { T objSelected = getSelection(); if (objSelected == null) return; InfoTableSelection selection = new InfoTableSelection(objSelected); final InfoTableEvent<T> event = new InfoTableEvent<T>(this, selection); Object[] listeners = deleteItemListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { @SuppressWarnings("unchecked") final IInfoTableDeleteItemListener<T> l = (IInfoTableDeleteItemListener<T>) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.deleteItem(event); } }); } } protected abstract Boolean canView(T target) throws ApplicationException; protected abstract Boolean canEdit(T target) throws ApplicationException; protected abstract Boolean canDelete(T target) throws ApplicationException; public void setList(final List<T> list) { this.list.setDelegate(list); } public void setCollection(final Collection<T> list) { this.list.setDelegate(new ArrayList<T>(list)); } public List<T> getList() { return list; } @Override public void addListChangeHandler(ListChangeHandler<T> handler) { list.addListChangeHandler(handler); } @Override public void removeListChangeHandler(ListChangeHandler<T> handler) { list.removeListChangeHandler(handler); } }