package edu.ualberta.med.biobank.widgets.trees.infos; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MenuAdapter; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Tree; import edu.ualberta.med.biobank.common.wrappers.DispatchSpecimenWrapper; import edu.ualberta.med.biobank.common.wrappers.ModelWrapper; import edu.ualberta.med.biobank.gui.common.BgcLogger; import edu.ualberta.med.biobank.treeview.AbstractAdapterBase; import edu.ualberta.med.biobank.treeview.Node; import edu.ualberta.med.biobank.treeview.util.AdapterFactory; import edu.ualberta.med.biobank.widgets.infotables.BiobankCollectionModel; import edu.ualberta.med.biobank.widgets.infotables.BiobankTableSorter; import edu.ualberta.med.biobank.widgets.trees.infos.listener.IInfoTreeAddItemListener; import edu.ualberta.med.biobank.widgets.trees.infos.listener.IInfoTreeDeleteItemListener; import edu.ualberta.med.biobank.widgets.trees.infos.listener.IInfoTreeEditItemListener; import edu.ualberta.med.biobank.widgets.trees.infos.listener.InfoTreeEvent; /** * Used to display tabular information for an object in the object model or * combined information from several objects in the object model. * <p> * The information in the table is loaded in a background thread. By loading * object model data in a background thread, the main UI thread is not blocked * when displaying the cells of the table. * <p> * This widget supports the following listeners: double click listener, edit * listener, and delete listener. The double click listener is invoked when the * user double clicks on a row in the table. The edit and delete listeners are * invoked via the table's context menu. When one of these listeners is * registered, the widget adds an "Edit" and / or "Delete" item to the context * menu. The corresponding listener is then invoked when the user selects either * one of the two menu choices. The event passed to the listener contains the * current selection for the table. * <p> * This widget also allows for a row of information to be copied to the * clipboard. The "Copy" command is made available in the context menu. When * this command is selected by the user the rows that are currently selected are * copied to the clipboard. * <p> * If neither the edit or delete listeners are registered, then the table is * configured to be in multi select mode and the selection of multiple lines is * available to the user. * <p> * NOTE: * <p> * Care should be taken in the label provider so that blocking calls are not * made to the object model. All calls to the object model should be done in * abstract method getCollectionModelObject(). * * @param <T> The model object wrapper the table is based on. * */ public abstract class InfoTreeWidget<T> extends AbstractInfoTreeWidget<T> { /* * see http://lekkimworld.com/2008/03/27/setting_table_row_height_in_swt * .html for how to set row height. */ private static BgcLogger logger = BgcLogger.getLogger(InfoTreeWidget.class .getName()); protected List<Node> model; protected ListenerList addItemListeners = new ListenerList(); protected ListenerList editItemListeners = new ListenerList(); protected ListenerList deleteItemListeners = new ListenerList(); protected ListenerList doubleClickListeners = new ListenerList(); private MenuItem editItem; private List<Node> modelSubList; public InfoTreeWidget(Composite parent, List<T> collection, String[] headings) { super(parent, collection, headings, null, 5); addTreeClickListener(); } public InfoTreeWidget(Composite parent, List<T> collection, String[] headings, int rowsPerPage) { super(parent, collection, headings, null, rowsPerPage); addTreeClickListener(); } private void addTreeClickListener() { treeViewer.addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { if (doubleClickListeners.size() > 0) { InfoTreeWidget.this.doubleClick(); } } }); menu.addMenuListener(new MenuAdapter() { @Override public void menuShown(MenuEvent e) { if (editItem == null) return; if (getSelection() instanceof ModelWrapper<?> && !(getSelection() instanceof DispatchSpecimenWrapper)) editItem.setEnabled(true); // TODO: cant check permission on unknown type! what to do? } }); } @Override protected void init(List<T> collection) { reloadData = true; model = new ArrayList<Node>(); initModel(collection); } @Override protected void setPaginationParams(List<T> collection) { if (pageInfo.rowsPerPage != 0 && (collection.size() > pageInfo.rowsPerPage)) { Double size = new Double(collection.size()); Double pageSize = new Double(pageInfo.rowsPerPage); pageInfo.pageTotal = Double.valueOf(Math.ceil(size / pageSize)) .intValue(); paginationRequired = true; if (pageInfo.page == pageInfo.pageTotal) pageInfo.page--; getTreeViewer().refresh(); } else paginationRequired = false; } /** * Derived classes should override this method if info table support editing * of items in the table. * * @return true if editing is allowed. */ @Override protected boolean isEditMode() { return false; } protected BiobankCollectionModel getSelectionInternal() { Assert.isTrue(!treeViewer.getTree().isDisposed(), "widget is disposed"); //$NON-NLS-1$ IStructuredSelection stSelection = (IStructuredSelection) treeViewer .getSelection(); return (BiobankCollectionModel) stSelection.getFirstElement(); } protected void initModel(List<T> collection) { if ((collection == null) || (model.size() == collection.size())) return; BiobankTableSorter comparator = getComparator(); if (comparator != null) Collections.sort(collection, comparator); model.clear(); for (int i = 0, n = collection.size(); i < n; ++i) { model.add(new BiobankCollectionModel(root, i)); } } protected abstract BiobankTableSorter getComparator(); protected abstract String getCollectionModelObjectToString(Object o); @Override public boolean setFocus() { treeViewer.getControl().setFocus(); return true; } /** * Should be used by info tables that allow editing of data. Use this method * instead of setCollection(). * * @param collection */ public void reloadCollection(final List<T> collection, T selection) { reloadData = true; setCollection(collection, selection); } public void reloadCollection(final List<T> collection) { reloadData = true; setCollection(collection, null); } @Override protected void treeLoader(final List<T> collection, final T selection) { final Tree tree = treeViewer.getTree(); Display display = tree.getDisplay(); initModel(collection); if (paginationRequired) { start = pageInfo.page * pageInfo.rowsPerPage; end = Math.min(start + pageInfo.rowsPerPage, model.size()); } else { start = 0; end = model.size(); } modelSubList = model.subList(start, end); display.syncExec(new Runnable() { @Override public void run() { if (!tree.isDisposed()) { treeViewer.refresh(); } } }); try { BiobankCollectionModel selItem = null; for (int i = start; i < end; ++i) { if (tree.isDisposed()) return; final BiobankCollectionModel item = (BiobankCollectionModel) model .get(i); Assert.isNotNull(item != null); if (reloadData || (item.o == null)) { item.o = getCollectionModelObject(collection .get(item.index)); } display.syncExec(new Runnable() { @Override public void run() { if (!tree.isDisposed()) { treeViewer.refresh(item, true); } } }); if ((selection != null) && selection.equals(item.o)) { selItem = item; } } reloadData = false; final BiobankCollectionModel selectedItem = selItem; display.syncExec(new Runnable() { @Override public void run() { if (!tree.isDisposed()) { if (paginationRequired) { enablePaginationWidget(true); } if (selectedItem != null) { treeViewer.setSelection(new StructuredSelection( selectedItem)); } } } }); } catch (Exception e) { logger.error("setCollection error", e); //$NON-NLS-1$ } } public Object getCollectionModelObject(Object item) throws Exception { return item; } public Object getSelection() { BiobankCollectionModel item = getSelectionInternal(); if (item == null) return null; Object type = item.o; Assert.isNotNull(type); return type; } public void addClickListener(IDoubleClickListener listener) { doubleClickListeners.add(listener); editItem = new MenuItem(getMenu(), SWT.PUSH); editItem.setText(Messages.InfoTreeWidget_edit_label); editItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { ModelWrapper<?> selection = (ModelWrapper<?>) InfoTreeWidget.this .getSelection(); if (selection != null) { AbstractAdapterBase adapter = AdapterFactory .getAdapter(selection); adapter.openEntryForm(); } } }); } public void doubleClick() { // get selection as derived class object Object selection = getSelection(); final DoubleClickEvent event = new DoubleClickEvent(treeViewer, new InfoTreeSelection(selection)); Object[] listeners = doubleClickListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { final IDoubleClickListener l = (IDoubleClickListener) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.doubleClick(event); } }); } } public void addAddItemListener(IInfoTreeAddItemListener<T> listener) { addItemListeners.add(listener); Assert.isNotNull(menu); MenuItem item = new MenuItem(menu, SWT.PUSH); item.setText(Messages.InfoTreeWidget_add_label); item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { addItem(); } }); } public void addEditItemListener(IInfoTreeEditItemListener<T> listener) { editItemListeners.add(listener); Assert.isNotNull(menu); MenuItem item = new MenuItem(menu, SWT.PUSH); item.setText(Messages.InfoTreeWidget_edit_label); item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { editItem(); } }); } public void addDeleteItemListener(IInfoTreeDeleteItemListener<T> listener) { deleteItemListeners.add(listener); Assert.isNotNull(menu); MenuItem item = new MenuItem(menu, SWT.PUSH); item.setText(Messages.InfoTreeWidget_delete_label); item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { deleteItem(); } }); } protected void addItem() { InfoTreeSelection selection = new InfoTreeSelection(getSelection()); final InfoTreeEvent<T> event = new InfoTreeEvent<T>(this, selection); Object[] listeners = addItemListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { @SuppressWarnings("unchecked") final IInfoTreeAddItemListener<T> l = (IInfoTreeAddItemListener<T>) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.addItem(event); } }); } } protected void editItem() { InfoTreeSelection selection = new InfoTreeSelection(getSelection()); final InfoTreeEvent<T> event = new InfoTreeEvent<T>(this, selection); Object[] listeners = editItemListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { @SuppressWarnings("unchecked") final IInfoTreeEditItemListener<T> l = (IInfoTreeEditItemListener<T>) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.editItem(event); } }); } } protected void deleteItem() { InfoTreeSelection selection = new InfoTreeSelection(getSelection()); final InfoTreeEvent<T> event = new InfoTreeEvent<T>(this, selection); Object[] listeners = deleteItemListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { @SuppressWarnings("unchecked") final IInfoTreeDeleteItemListener<T> l = (IInfoTreeDeleteItemListener<T>) listeners[i]; SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.deleteItem(event); } }); } } @Override protected void enableWidgets(boolean enable) { if (enable && (pageInfo.page > 0)) { firstButton.setEnabled(true); prevButton.setEnabled(true); } else { firstButton.setEnabled(false); prevButton.setEnabled(false); } if (enable && (pageInfo.page < pageInfo.pageTotal - 1)) { lastButton.setEnabled(true); nextButton.setEnabled(true); } else { lastButton.setEnabled(false); nextButton.setEnabled(false); } } @Override protected void setDefaultWidgetsEnabled() { firstButton.setEnabled(false); prevButton.setEnabled(false); } @Override protected void firstPage() { pageInfo.page = 0; firstButton.setEnabled(false); prevButton.setEnabled(false); lastButton.setEnabled(true); nextButton.setEnabled(true); } @Override protected void lastPage() { pageInfo.page = pageInfo.pageTotal - 1; firstButton.setEnabled(true); prevButton.setEnabled(true); lastButton.setEnabled(false); nextButton.setEnabled(false); } @Override protected void prevPage() { if (pageInfo.page == 0) return; pageInfo.page--; if (pageInfo.page == 0) { firstButton.setEnabled(false); prevButton.setEnabled(false); } if (pageInfo.page == pageInfo.pageTotal - 2) { lastButton.setEnabled(true); nextButton.setEnabled(true); } } @Override protected void nextPage() { if (pageInfo.page >= pageInfo.pageTotal) return; pageInfo.page++; if (pageInfo.page == 1) { firstButton.setEnabled(true); prevButton.setEnabled(true); } if (pageInfo.page == pageInfo.pageTotal - 1) { lastButton.setEnabled(false); nextButton.setEnabled(false); } } @Override protected void setPageLabelText() { pageLabel.setText(NLS.bind(Messages.InfoTreeWidget_pages_text, (pageInfo.page + 1), +pageInfo.pageTotal)); } @Override protected List<Node> getRootChildren() { if (modelSubList == null) return Collections.emptyList(); return modelSubList; } @Override protected List<Node> getNodeChildren(Node node) throws Exception { if (node == root) { return node.getChildren(); } return Collections.emptyList(); } protected List<Node> createNodes(Node parent, List<?> objects) throws Exception { List<Node> nodes = new ArrayList<Node>(); for (int i = 0, n = objects.size(); i < n; ++i) { BiobankCollectionModel model = new BiobankCollectionModel(parent, i); model.o = getCollectionModelObject(objects.get(i)); nodes.add(model); } return nodes; } }