/* ****************************************************************************** * Copyright (c) 2006-2015 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.viewers; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.util.OpenStrategy; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Widget; public class MListViewer extends StructuredViewer { private class MListLayout extends Layout { @Override protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { IListLayout layout = getListLayout(); if (layout != null) return layout.computeSize(MListViewer.this, composite, wHint, hHint, flushCache); return new Point(0, 0); } @Override protected void layout(Composite composite, boolean flushCache) { IListLayout layout = getListLayout(); if (layout != null) layout.layout(MListViewer.this, composite, flushCache); } } private Composite composite; private Listener eventListener = new Listener() { public void handleEvent(Event event) { if (event.type == SWT.Selection) { handleItemSelect(event); } else if (event.type == SWT.DefaultSelection) { handleOpen(new SelectionEvent(event)); } else if (event.type == SWT.MouseDown) { handleMouseDown(event); } else if (event.type == SWT.KeyDown) { handleKeyDown(event); } else if (event.type == SWT.Traverse) { handleKeyTraversed(event); } } }; private int keyDownSelectionEventId = 0; public MListViewer(Composite parent, int style) { this.composite = new Composite(parent, style); this.composite.setLayout(new MListLayout()); this.composite.addListener(SWT.Selection, eventListener); this.composite.addListener(SWT.DefaultSelection, eventListener); this.composite.addListener(SWT.MouseDown, eventListener); this.composite.addListener(SWT.KeyDown, eventListener); this.composite.addListener(SWT.Traverse, eventListener); hookControl(this.composite); } // public void setMargins(int left, int top, int right, int bottom) { // GridLayout layout = ((GridLayout) this.composite.getLayout()); // layout.marginLeft = left; // layout.marginTop = top; // layout.marginRight = right; // layout.marginBottom = bottom; // this.composite.layout(true); // } // // public void setSpacing(int spacing) { // GridLayout layout = ((GridLayout) this.composite.getLayout()); // layout.horizontalSpacing = spacing; // layout.verticalSpacing = spacing; // this.composite.layout(true); // } // // public void setItemHints(int wHint, int hHint) { // if (wHint == itemHints.x && hHint == itemHints.y) // return; // // itemHints.x = wHint; // itemHints.y = hHint; // // for (Control item : getItems()) { // ((GridData) item.getLayoutData()).widthHint = itemHints.x; // ((GridData) item.getLayoutData()).heightHint = itemHints.y; // } // this.composite.layout(true); // } @Override public void setLabelProvider(IBaseLabelProvider labelProvider) { Assert.isTrue(labelProvider instanceof IListRenderer); super.setLabelProvider(labelProvider); } @Override protected Widget doFindInputItem(Object element) { if (element != null && equals(element, getRoot())) { return getControl(); } return null; } @Override protected Widget doFindItem(Object element) { Control[] items = getItems(); for (int i = 0; i < items.length; i++) { Control item = items[i]; Object data = item.getData(); if (data != null && equals(data, element)) { return item; } } return null; } @Override protected void doUpdateItem(Widget widget, Object element, boolean fullMap) { if (widget instanceof Control) { Control item = (Control) widget; // remember element we are showing if (fullMap) { associate(element, item); } else { Object data = item.getData(); if (data != null) { unmapElement(data, item); } item.setData(element); mapElement(element, item); } IListRenderer renderer = (IListRenderer) getLabelProvider(); if (renderer != null) { renderer.updateListItem(this, element, item); } if (item.isDisposed()) { unmapElement(element, item); } } } @SuppressWarnings("unchecked") @Override protected List getSelectionFromWidget() { List list = new ArrayList(); IListRenderer renderer = (IListRenderer) getLabelProvider(); if (renderer == null) return list; Control[] items = getItems(); for (int i = 0; i < items.length; i++) { Control item = items[i]; int state = renderer.getListItemState(this, item); if ((state & IListRenderer.STATE_SELECTED) != 0) { Object e = item.getData(); if (e != null) { list.add(e); } } } return list; } @Override protected void internalRefresh(Object element) { if (element == null || equals(element, getRoot())) { internalRefreshAll(element); } else { Widget item = findItem(element); if (item != null) { updateItem(item, element); } } } private void internalRefreshAll(Object inputElement) { Object[] elements = getSortedChildren(inputElement); List<Control> items = new ArrayList<Control>(Arrays.asList(getItems())); Control item; Object element; int itemIndex; int i; for (i = 0; i < elements.length; i++) { element = elements[i]; // search for matching item itemIndex = -1; for (int j = i; j < items.size(); j++) { item = items.get(j); if (equals(element, item.getData())) { itemIndex = j; break; } } if (itemIndex < 0) { // item not found, create a new one item = createItem(element); Assert.isTrue(item != null && !item.isDisposed()); items.add(i, item); if (i == 0) { item.moveAbove(null); } else { item.moveBelow(items.get(i - 1)); } } else { item = items.get(itemIndex); if (itemIndex > i) { // item is after element, move it if (i == 0) { item.moveAbove(null); } else { item.moveBelow(items.get(i - 1)); } items.remove(itemIndex); items.add(i, item); } } IListRenderer renderer = (IListRenderer) getLabelProvider(); if (renderer != null) { renderer.updateListItem(this, element, item); } } for (; i < items.size(); i++) { // remove unused items item = items.get(i); disassociate(item); IListLayout layout = getListLayout(); if (layout != null) layout.itemRemoved(this, composite, item); item.dispose(); } composite.layout(true); } private Control createItem(Object element) { Control item; IListRenderer renderer = (IListRenderer) getLabelProvider(); if (renderer != null) { item = renderer.createListItemForElement(this, composite, element); } else { item = new Composite(composite, SWT.NONE); } IListLayout layout = getListLayout(); if (layout != null) layout.itemAdded(this, composite, item); associate(element, item); return item; } private IListLayout getListLayout() { IListRenderer renderer = (IListRenderer) getLabelProvider(); return renderer.getListLayout(this); } @Override public void reveal(Object element) { } @Override protected void setSelectionToWidget(List list, boolean reveal) { IListRenderer renderer = (IListRenderer) getLabelProvider(); if (renderer == null) return; @SuppressWarnings("unchecked") List<Object> elementsToSelect = new ArrayList<Object>(list); Control[] items = getItems(); for (int i = 0; i < items.length; i++) { Control item = items[i]; Object data = item.getData(); int elementIndex = -1; if (data != null) { for (int j = 0; j < elementsToSelect.size(); j++) { if (equals(data, elementsToSelect.get(j))) { elementIndex = j; } } } int state = renderer.getListItemState(this, item); if (elementIndex < 0) { state &= ~IListRenderer.STATE_SELECTED; } else { state |= IListRenderer.STATE_SELECTED; elementsToSelect.remove(elementIndex); } renderer.setListItemState(this, item, state); } } @Override public Control getControl() { return this.composite; } /** * Associates the given element with the given widget. Sets the given item's * data to be the element, and maps the element to the item in the element * map (if enabled). * * @param element * the element * @param item * the widget */ protected void associate(Object element, Control item) { Object data = item.getData(); if (data != element) { if (data != null) { disassociate(item); } item.setData(element); mapElement(element, item); } else { // Always map the element, even if data == element, // since unmapAllElements() can leave the map inconsistent // See bug 2741 for details. mapElement(element, item); } } /** * Disassociates the given SWT item from its corresponding element. Sets the * item's data to <code>null</code> and removes the element from the element * map (if enabled). * * @param item * the widget */ protected void disassociate(Control item) { Object element = item.getData(); Assert.isNotNull(element); //Clear the map before we clear the data unmapElement(element, item); item.setData(null); } /** * Returns the element with the given index from this list viewer. Returns * <code>null</code> if the index is out of range. * * @param index * the zero-based index * @return the element at the given index, or <code>null</code> if the index * is out of range */ public Object getElementAt(int index) { Control[] items = getItems(); if (index >= 0 && index < items.length) { return items[index].getData(); } return null; } protected Control[] getItems() { return this.composite.getChildren(); } @Override protected void inputChanged(final Object input, Object oldInput) { super.inputChanged(input, oldInput); preservingSelection(new Runnable() { public void run() { final Control control = getControl(); control.setRedraw(false); try { internalRefreshAll(input); } finally { control.setRedraw(true); } } }); } private void handleItemSelect(final Event event) { handleSelect(new SelectionEvent(event)); if (event.detail == SWT.ARROW_DOWN || event.detail == SWT.ARROW_UP || event.detail == SWT.ARROW_LEFT || event.detail == SWT.ARROW_RIGHT) { keyDownSelectionEventId++; final int id = keyDownSelectionEventId; Display.getCurrent().timerExec(OpenStrategy.getPostSelectionDelay(), new Runnable() { public void run() { if (id != keyDownSelectionEventId) return; handlePostSelect(new SelectionEvent(event)); } }); } else { Display.getCurrent().asyncExec(new Runnable() { public void run() { handlePostSelect(new SelectionEvent(event)); } }); } } private void handleMouseDown(Event event) { getControl().setFocus(); } private void handleKeyTraversed(Event event) { switch (event.detail) { case SWT.TRAVERSE_ARROW_NEXT: selectNextItem(); event.doit = false; return; case SWT.TRAVERSE_ARROW_PREVIOUS: selectPreviousItem(); event.doit = false; return; case SWT.TRAVERSE_PAGE_NEXT: case SWT.TRAVERSE_PAGE_PREVIOUS: case SWT.TRAVERSE_RETURN: event.doit = false; return; } event.doit = true; } private void handleKeyDown(Event event) { if (event.character == '\r') { handleOpen(new SelectionEvent(event)); } } private void selectNextItem() { IListRenderer renderer = (IListRenderer) getLabelProvider(); if (renderer == null) return; Control[] items = getItems(); if (items.length == 0) return; Control nextItem = null; for (int i = items.length - 1; i >= 0; i--) { Control item = items[i]; int state = renderer.getListItemState(this, item); if ((state & IListRenderer.STATE_SELECTED) != 0) break; nextItem = item; } if (nextItem == null) { nextItem = items[items.length - 1]; } selectSingle(renderer, items, nextItem); } private void selectPreviousItem() { IListRenderer renderer = (IListRenderer) getLabelProvider(); if (renderer == null) return; Control[] items = getItems(); if (items.length == 0) return; Control prevItem = null; for (int i = 0; i < items.length; i++) { Control item = items[i]; int state = renderer.getListItemState(this, item); if ((state & IListRenderer.STATE_SELECTED) != 0) break; prevItem = item; } if (prevItem == null) { prevItem = items[0]; } selectSingle(renderer, items, prevItem); } private void selectSingle(IListRenderer renderer, Control[] items, Control itemToSelect) { for (int i = 0; i < items.length; i++) { Control item = items[i]; int state = renderer.getListItemState(this, item); if (item != itemToSelect) { state &= ~IListRenderer.STATE_SELECTED; } else { state |= IListRenderer.STATE_SELECTED; } renderer.setListItemState(this, item, state); } } }