blob: 64731a88531345179582a8d006f1d2f9c9b1e9aa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2011 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.commons.ui;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.ViewerComparator;
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.ControlListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
/**
* Based on {@link org.eclipse.ui.internal.progress.DetailedProgressViewer}.
*
* @author Steffen Pingel
* @since 3.7
*/
@SuppressWarnings("restriction")
public abstract class ControlListViewer extends StructuredViewer {
Composite control;
private final ScrolledComposite scrolled;
private final Composite noEntryArea;
protected boolean hasFocus;
/**
* Create a new instance of the receiver with a control that is a child of parent with style style.
*
* @param parent
* @param style
*/
public ControlListViewer(Composite parent, int style) {
scrolled = new ScrolledComposite(parent, style);
int height = JFaceResources.getDefaultFont().getFontData()[0].getHeight();
scrolled.getVerticalBar().setIncrement(height * 2);
scrolled.setExpandHorizontal(true);
scrolled.setExpandVertical(true);
control = new Composite(scrolled, SWT.NONE) {
@Override
public boolean setFocus() {
forceFocus();
return true;
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible) {
updateSize(control);
}
}
};
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.horizontalSpacing = 0;
layout.verticalSpacing = 1;
control.setLayout(layout);
control.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
control.addControlListener(new ControlListener() {
public void controlMoved(ControlEvent e) {
updateVisibleItems();
}
public void controlResized(ControlEvent e) {
updateVisibleItems();
}
});
scrolled.setContent(control);
hookControl(control);
noEntryArea = new Composite(scrolled, SWT.NONE);
doCreateNoEntryArea(noEntryArea);
scrolled.setExpandHorizontal(true);
scrolled.setExpandVertical(true);
scrolled.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
updateSize(scrolled.getContent());
}
});
control.addTraverseListener(new TraverseListener() {
private boolean handleEvent = true;
public void keyTraversed(TraverseEvent event) {
if (!handleEvent) {
return;
}
switch (event.detail) {
case SWT.TRAVERSE_ARROW_PREVIOUS: {
Control[] children = control.getChildren();
if (children.length > 0) {
boolean selected = false;
for (int i = 0; i < children.length; i++) {
ControlListItem item = (ControlListItem) children[i];
if (item.isSelected()) {
selected = true;
if (i > 0) {
setSelection(new StructuredSelection(children[i - 1].getData()), true);
}
break;
}
}
if (!selected) {
setSelection(new StructuredSelection(children[children.length - 1].getData()), true);
}
}
break;
}
case SWT.TRAVERSE_ARROW_NEXT: {
Control[] children = control.getChildren();
if (children.length > 0) {
boolean selected = false;
for (int i = 0; i < children.length; i++) {
ControlListItem item = (ControlListItem) children[i];
if (item.isSelected()) {
selected = true;
if (i < children.length - 1) {
setSelection(new StructuredSelection(children[i + 1].getData()), true);
}
break;
}
}
if (!selected) {
setSelection(new StructuredSelection(children[0].getData()), true);
}
}
break;
}
default:
handleEvent = false;
event.doit = true;
Control control = ControlListViewer.this.control;
Shell shell = control.getShell();
while (control != null) {
if (control.traverse(event.detail)) {
break;
}
if (!event.doit || control == shell) {
break;
}
control = control.getParent();
}
handleEvent = true;
break;
}
}
});
}
protected void doCreateNoEntryArea(Composite parent) {
}
public void add(Object[] elements) {
ViewerComparator sorter = getComparator();
// Use a Set in case we are getting something added that exists
Set<Object> newItems = new HashSet<Object>(elements.length);
Control[] existingChildren = control.getChildren();
for (Control element : existingChildren) {
if (element.getData() != null) {
newItems.add(element.getData());
}
}
for (Object element : elements) {
if (element != null) {
newItems.add(element);
}
}
Object[] infos = new Object[newItems.size()];
newItems.toArray(infos);
if (sorter != null) {
sorter.sort(this, infos);
}
// Update with the new elements to prevent flash
for (Control element : existingChildren) {
((ControlListItem) element).dispose();
}
for (int i = 0; i < infos.length; i++) {
ControlListItem item = createNewItem(infos[i]);
item.updateColors(i);
}
control.layout(true);
doUpdateContent();
}
private void updateSize(Control control) {
if (control == null) {
return;
}
// XXX need a small offset in case the list has a scroll bar
Point size = control.computeSize(scrolled.getClientArea().width - 20, SWT.DEFAULT, true);
control.setSize(size);
scrolled.setMinSize(size);
}
protected void doUpdateContent() {
if (control.getChildren().length > 0) {
updateSize(control);
scrolled.setContent(control);
} else {
updateSize(noEntryArea);
scrolled.setContent(noEntryArea);
}
}
/**
* Create a new item for info.
*
* @param element
* @return ControlListItem
*/
private ControlListItem createNewItem(Object element) {
final ControlListItem item = doCreateItem(control, element);
// item.getChildren()[0].addPaintListener(new PaintListener() {
// public void paintControl(PaintEvent e) {
// if (hasFocus && item.isSelected()) {
// Point size = item.getSize();
// e.gc.setForeground(e.gc.getDevice().getSystemColor(SWT.COLOR_DARK_GRAY));
// e.gc.setLineDash(new int[] { 1, 2 });
// e.gc.drawRoundRectangle(0, 0, size.x - 1, size.y - 1, 5, 5);
// }
// }
// });
item.setIndexListener(new ControlListItem.IndexListener() {
public void selectNext() {
Control[] children = control.getChildren();
for (int i = 0; i < children.length; i++) {
if (item == children[i]) {
if (i < children.length - 1) {
setSelection(new StructuredSelection(children[i + 1].getData()));
}
break;
}
}
}
public void selectPrevious() {
Control[] children = control.getChildren();
for (int i = 0; i < children.length; i++) {
if (item == children[i]) {
if (i > 0) {
setSelection(new StructuredSelection(children[i - 1].getData()));
}
break;
}
}
}
public void select() {
setSelection(new StructuredSelection(item.getData()));
setFocus();
}
public void open() {
handleOpen();
}
});
// Refresh to populate with the current tasks
item.refresh();
return item;
}
protected abstract ControlListItem doCreateItem(Composite parent, Object element);
@Override
protected ControlListItem doFindInputItem(Object element) {
return null;
}
@Override
protected ControlListItem doFindItem(Object element) {
Control[] children = control.getChildren();
for (Control child : children) {
if (child.isDisposed() || child.getData() == null) {
continue;
}
if (child.getData().equals(element)) {
return (ControlListItem) child;
}
}
return null;
}
@Override
protected void doUpdateItem(Widget item, Object element, boolean fullMap) {
if (usingElementMap()) {
unmapElement(item);
}
item.dispose();
add(new Object[] { element });
}
@Override
public ScrolledComposite getControl() {
return scrolled;
}
@Override
protected List<?> getSelectionFromWidget() {
Control[] children = control.getChildren();
ArrayList<Object> selection = new ArrayList<Object>(children.length);
for (Control child : children) {
ControlListItem item = (ControlListItem) child;
if (item.isSelected() && item.getData() != null) {
selection.add(item.getData());
}
}
return selection;
}
protected void handleOpen() {
Control control = getControl();
if (control != null && !control.isDisposed()) {
ISelection selection = getSelection();
fireOpen(new OpenEvent(this, selection));
}
}
@Override
protected void inputChanged(Object input, Object oldInput) {
super.inputChanged(input, oldInput);
refreshAll();
doUpdateContent();
}
@Override
protected void internalRefresh(Object element) {
if (element == null) {
return;
}
if (element.equals(getRoot())) {
refreshAll();
return;
}
Widget widget = findItem(element);
if (widget == null) {
add(new Object[] { element });
return;
}
((ControlListItem) widget).refresh();
updateSize(control);
}
public void remove(Object[] elements) {
for (Object element : elements) {
Widget item = doFindItem(element);
if (item != null) {
unmapElement(element);
item.dispose();
}
}
Control[] existingChildren = control.getChildren();
for (int i = 0; i < existingChildren.length; i++) {
ControlListItem item = (ControlListItem) existingChildren[i];
item.updateColors(i);
}
control.layout(true);
doUpdateContent();
}
@Override
public void reveal(Object element) {
Control control = doFindItem(element);
if (control != null) {
revealControl(control);
}
}
private void revealControl(Control control) {
Rectangle clientArea = scrolled.getClientArea();
Point origin = scrolled.getOrigin();
Point location = control.getLocation();
Point size = control.getSize();
if (location.y + size.y > origin.y + clientArea.height) {
scrolled.setOrigin(origin.x, location.y + size.y - clientArea.height);
}
if (location.y < origin.y) {
scrolled.setOrigin(origin.x, location.y);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected void setSelectionToWidget(List list, boolean reveal) {
HashSet<Object> elements = new HashSet<Object>(list);
Control[] children = control.getChildren();
for (Control control : children) {
ControlListItem child = (ControlListItem) control;
boolean selected = elements.contains(child.getData());
if (selected != child.isSelected()) {
child.setSelected(selected);
}
if (reveal && selected) {
revealControl(child);
reveal = false;
}
}
}
/**
* Set focus on the current selection.
*/
public void setFocus() {
Control[] children = control.getChildren();
if (children.length > 0) {
// causes the item's tool bar to get focus when clicked which is undesirable
// for (Control element : children) {
// ControlListItem item = (ControlListItem) element;
// if (item.isSelected()) {
// if (item.setFocus()) {
// return;
// }
// }
// }
control.forceFocus();
} else {
noEntryArea.setFocus();
}
}
/**
* Refresh everything as the root is being refreshed.
*/
private void refreshAll() {
Object[] infos = getSortedChildren(getRoot());
Control[] existingChildren = control.getChildren();
for (Control element : existingChildren) {
element.dispose();
}
for (int i = 0; i < infos.length; i++) {
ControlListItem item = createNewItem(infos[i]);
item.updateColors(i);
}
control.layout(true);
doUpdateContent();
}
/**
* Set the virtual items to be visible or not depending on the displayed area.
*/
private void updateVisibleItems() {
Control[] children = control.getChildren();
int top = scrolled.getOrigin().y;
int bottom = top + scrolled.getParent().getBounds().height;
for (Control element : children) {
ControlListItem item = (ControlListItem) element;
item.setDisplayed(top, bottom);
}
}
}