package com.netifera.platform.ui.tasks.list; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.widgets.ColumnLayout; /** * Viewer to show a collection of elements in a ScrolledComposite with any custom widget * to show each item. Extends StructuredViewer * */ public class ItemViewer extends StructuredViewer { static String DARK_COLOR = "com.netifera.platform.ui.DARK_COLOR_LIST"; static { // Mac has different Gamma value int shift = "carbon".equals(SWT.getPlatform()) ? -25 : -10;//$NON-NLS-1$ final Color lightColor = PlatformUI.getWorkbench().getDisplay() .getSystemColor(SWT.COLOR_LIST_BACKGROUND); // Determine a dark color by shifting the list color RGB darkRGB = new RGB(Math.max(0, lightColor.getRed() + shift), Math .max(0, lightColor.getGreen() + shift), Math.max(0, lightColor .getBlue() + shift)); JFaceResources.getColorRegistry().put(DARK_COLOR, darkRGB); } /* map between data elements and UI items widgets */ private Map<Object,Widget> itemMap = new ConcurrentHashMap<Object,Widget>(); /* provides UI widgets to represent elements */ private IItemProvider itemProvider; // private List<ISelectionChangedListener> listeners = new ArrayList<ISelectionChangedListener>(); private List<Object> selection = new ArrayList<Object>(); private Composite control; private ScrolledComposite scrolled; private volatile boolean busy; /** * Create the composite * @param parent * @param style */ public ItemViewer(Composite parent, int style) { int height = JFaceResources.getDefaultFont().getFontData()[0].getHeight(); scrolled = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); scrolled.setLayout(new FillLayout()); control = new Composite(scrolled, SWT.NONE); control.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); scrolled.setContent(control); ColumnLayout columnLayout = new ColumnLayout(); /* number of columns in the form, set to one to avoid composites side by side */ columnLayout.maxNumColumns = 1; columnLayout.minNumColumns = 1; columnLayout.horizontalSpacing = 1; columnLayout.verticalSpacing = 0; columnLayout.leftMargin = 0; columnLayout.rightMargin = 0; columnLayout.topMargin = 0; columnLayout.bottomMargin = 0; control.setLayout(columnLayout); // toolkit.paintBordersFor(control); scrolled.addControlListener(new ControlAdapter() { public void controlResized(ControlEvent e) { Rectangle r = scrolled.getClientArea(); scrolled.setMinSize(control.computeSize(r.width, SWT.DEFAULT)); } }); scrolled.getVerticalBar().setIncrement(height * 2); scrolled.setExpandHorizontal(true); scrolled.setExpandVertical(true); } public void dispose() { /*XXX dispose widgets ? */ if(itemProvider != null) { itemProvider.dispose(); itemProvider = null; } } /** * Returns a UI widget for the given model element using the UI item provider * * @param element data object from model */ private Widget createItemWidget (Object element) { if(itemProvider == null) { return null; } /* call item provider to create the widget */ Widget item = itemProvider.getItem(element); if(item == null) { return null; } final Object finalElement = element; final Control itemControl = ((Control)item); /*add listener to set the selection when the widget is focused */ ((Control)item).addFocusListener(new FocusListener() { public void focusGained(FocusEvent e) { if(e.widget.getData() != null) { setSelection(new StructuredSelection(e.widget.getData()),true); } else { setSelection(new StructuredSelection(finalElement),true); } } public void focusLost(FocusEvent e) { } }); /* set focus when the item is clicked, to trigger selection */ MouseListener mouseListener = new MouseListener() { public void mouseDown(MouseEvent e) { itemControl.forceFocus(); } public void mouseDoubleClick(MouseEvent e) {} public void mouseUp(MouseEvent e) { } }; itemControl.addMouseListener(mouseListener); /*add the new item to the map*/ itemMap.put(element,item); return item; } /** * Updates the UI item for the given element * @param element not null */ protected synchronized void internalRefresh(Object element) { Assert.isNotNull(itemProvider); if(control.isDisposed()) { return; } /* if element is null or is the input/root item then refresh all */ if(element == null || element.equals(getRoot())) { internalRefreshAll(); return; } if(itemMap.containsKey(element)) { Widget item = itemMap.get(element); if(!item.isDisposed()) { doUpdateItem(item,element,true); } else { /* widget disposed what to do? remove from map? or nothing */ itemMap.remove(element); } } else /* new element, create item and set it as new selection*/ { /* if the items are sorted we have to create all the widgets again */ //TODO: is possible to avoid it? if(this.getComparator() != null) { internalRefreshAll(); } else { /*TODO itemProvider could return a hint if layout again is necessary or not */ Widget item = createItemWidget(element); ((Control)item).setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_YELLOW)); // control.layout(true); } setSelection(new StructuredSelection(element),true); } } /** * Create widgets from the current input, existing widgets are disposed. */ private void internalRefreshAll() { if(isBusy() || control.isDisposed() || getInput() == null || getContentProvider() == null) { return; } setBusy(true); control.setRedraw(false); control.getParent().setRedraw(false); /* dispose current item widgets saving their state */ for(Object element : itemMap.keySet()) { Widget item = itemMap.get(element); item.dispose(); itemMap.remove(element); } Object[] elements = getSortedChildren(getInput()); assertElementsNotNull(elements); /* create a new widget for each element and set saved state */ int i = elements.length%2; for(Object element : elements) { Widget item = createItemWidget(element); ((Control)item).setBackground(getItemBackgroundColor(i)); i++; } control.layout(true); scrolled.setMinSize(control.computeSize(scrolled.getClientArea().width, SWT.DEFAULT)); /* update size and position of controls */ control.setRedraw(true); control.getParent().setRedraw(true); setBusy(false); } private Color getItemBackgroundColor(int i) { if(i%2 == 0) { return Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND); } else { return JFaceResources.getColorRegistry().get(DARK_COLOR); } } protected boolean isBusy() { return busy; } protected void setBusy(boolean busy) { this.busy = busy; } public IItemProvider getItemProvider() { return itemProvider; } /** * Sets the viewer label provider, if the item provider is set it is also set for it. */ public void setLabelProvider(IBaseLabelProvider labelProvider) { Assert.isTrue(labelProvider instanceof ILabelProvider); super.setLabelProvider(labelProvider); if(itemProvider != null) { itemProvider.setLabelProvider((ILabelProvider)labelProvider); } } public void setItemProvider(IItemProvider itemProvider) { itemProvider.setParent(control); this.itemProvider = itemProvider; if(getLabelProvider() != null) { itemProvider.setLabelProvider((ILabelProvider)getLabelProvider()); } } /** StructuredViewer inputChanged "hook" method. * Remove previous items and clear selection */ protected void inputChanged(Object input, Object oldInput) { if(input == oldInput) { return; } /* empty selection */ selection.clear(); setSelection(StructuredSelection.EMPTY); /* dispose current item widgets */ for(Object element : itemMap.keySet()) { itemMap.get(element).dispose(); itemMap.remove(element); } //FIX? internalRefreshAll(); } public Control getControl() { return control; } public void update(Object element, String[] properties) { refresh(element); } /** Selection related methods */ private void setSelectionInternal(Object element, boolean reveal) { if(element != null && itemMap.containsKey(element)) { Widget item = itemMap.get(element); if(reveal && item instanceof Control && !item.isDisposed()) { ((Control)item).setFocus(); } selection.clear(); selection.add(element); } } /** * Searches for the specified data object and scroll the list to show it if necessary * @param element data object to search and show. */ public void reveal(Object element) { setSelectionInternal(element,true); } @SuppressWarnings("unchecked") protected List getSelectionFromWidget() { return selection; } @SuppressWarnings("unchecked") protected void setSelectionToWidget(List list, boolean reveal) { if(list.isEmpty()) { return; } //TODO change this code to allow multiple selection setSelectionInternal(list.get(0),reveal); } protected Widget doFindInputItem(Object element) { return null; } protected Widget doFindItem(Object element) { return itemMap.containsKey(element) ? itemMap.get(element) : null; } protected void doUpdateItem(Widget item, Object element, boolean fullMap) { itemProvider.updateItem(item, element); control.layout(true,true); /* control.layout(new Control[]{(Control)item}); //TODO: check if item size changed and update scrollable height, does the following computeSize code work? Control ci = (Control)item; Point s1 = ci.computeSize(SWT.DEFAULT, SWT.DEFAULT); itemProvider.updateItem(item, element); Point s2 = ci.computeSize(SWT.DEFAULT, SWT.DEFAULT); if(!s1.equals(s2)) { // control.layout(new Control[]{(Control)item}); control.layout(true); } */ } }