package com.peterhi.ui.newbars;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.jdesktop.core.animation.timing.Animator;
import org.jdesktop.core.animation.timing.TimingSource;
import org.jdesktop.swt.animation.timing.sources.SWTTimingSource;
import com.peterhi.ui.UIImageResource;
public final class StripBar extends Canvas implements Listener {
protected int marginLeft = 5;
protected int marginRight = 5;
protected int marginTop = 5;
protected int marginBottom = 5;
protected int itemSpacing = 5;
protected int dragThreshold = 15;
private final List<StripItem> items = new ArrayList<StripItem>();
private StripBarDragContext dragContext;
public StripBar(Composite parent, int style) {
super(parent, style);
addListener(SWT.SetData, this);
addListener(SWT.Paint, this);
addListener(SWT.PaintItem, this);
addListener(SWT.MouseEnter, this);
addListener(SWT.MouseExit, this);
addListener(SWT.MouseDown, this);
addListener(SWT.MouseMove, this);
addListener(SWT.MouseUp, this);
addListener(SWT.Dispose, this);
}
@Override
public void handleEvent(Event event) {
if (equals(event.widget)) {
if (event.type == SWT.SetData) {
onSetData(event);
} else if (event.type == SWT.Paint) {
onPaint(event);
} else if (event.type == SWT.PaintItem) {
onPaintItem(event);
} else if (event.type == SWT.MouseEnter) {
onMouseEnter(event);
} else if (event.type == SWT.MouseExit) {
onMouseExit(event);
} else if (event.type == SWT.MouseDown) {
onMouseDown(event);
} else if (event.type == SWT.MouseMove) {
onMouseMove(event);
} else if (event.type == SWT.MouseUp) {
onMouseUp(event);
} else if (event.type == SWT.Dispose) {
onDispose(event);
}
}
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
Point size = computeInternalSize();
Rectangle boundsWithTrim = computeTrim(0, 0, size.x, size.y);
size.x = boundsWithTrim.width;
size.y = boundsWithTrim.height;
return size;
}
public boolean isMultiSelection() {
int style = getStyle();
boolean multi = (style & SWT.MULTI) == SWT.MULTI;
return multi;
}
public StripItem[] getItems() {
return items.toArray(new StripItem[items.size()]);
}
public StripItem getItem(int x, int y) {
for (StripItem item : items) {
Rectangle bounds = item.getBounds();
if (bounds.contains(x, y)) {
return item;
}
}
return null;
}
public StripItem getItem(StripItemState state) {
StripItem[] items = getItems(state);
if (items.length > 0) {
return items[0];
}
return null;
}
public StripItem[] getItems(StripItemState state) {
Set<StripItem> itemSet = getItemSet(state);
return itemSet.toArray(new StripItem[itemSet.size()]);
}
protected void onSetData(Event event) {
StripItem item = (StripItem )event.item;
if (event.detail == SWT.INSERT) {
onAddItem(item, event.index);
} else if (event.detail == SWT.DEL) {
onRemoveItem(item);
}
}
protected void onPaint(Event event) {
GC gc = event.gc;
StripItem draggedItem = getItem(StripItemState.DRAGGED);
for (StripItem item : items) {
if (!item.equals(draggedItem)) {
firePaintItem(gc, item);
}
}
if (draggedItem != null) {
firePaintItem(gc, draggedItem);
}
}
protected void firePaintItem(GC gc, StripItem item) {
Rectangle bounds = item.getBounds();
Event redirEvent = new Event();
redirEvent.time = (int )(System.currentTimeMillis() & 0xffffffffL);
redirEvent.widget = this;
redirEvent.item = item;
redirEvent.gc = gc;
redirEvent.x = bounds.x;
redirEvent.y = bounds.y;
redirEvent.width = bounds.width;
redirEvent.height = bounds.height;
notifyListeners(SWT.PaintItem, redirEvent);
}
protected void onPaintItem(Event event) {
StripItem item = (StripItem )event.item;
Event redirEvent = new Event();
redirEvent.time = (int )(System.currentTimeMillis() & 0xffffffffL);
redirEvent.widget = item;
redirEvent.gc = event.gc;
redirEvent.x = event.x + item.getOffset();
redirEvent.y = event.y;
redirEvent.width = event.width;
redirEvent.height = event.height;
item.notifyListeners(SWT.Paint, redirEvent);
}
protected void onMouseEnter(Event event) {
onFocusItem(getItem(event.x, event.y));
}
protected void onMouseExit(Event event) {
onFocusItem(getItem(event.x, event.y));
}
protected void onMouseDown(Event event) {
if (event.button == 1) {
if (dragContext == null) {
StripItem item = getItem(event.x, event.y);
if (item != null) {
Rectangle bounds = item.getBounds();
dragContext = new StripBarDragContext(this, item);
dragContext.setMouseDownLocation(event.x, event.y);
dragContext.setMouseDownOffset(event.x - bounds.x, event.y - bounds.y);
dragContext.setMouseMoveLocation(event.x, event.y);
item.setState(StripItemState.PRESSED, false);
}
}
}
}
protected void onMouseMove(Event event) {
onFocusItem(getItem(event.x, event.y));
if (dragContext != null) {
Point mouseDownDistance = dragContext.getMouseDownDistance(event.x, event.y);
Point mouseMoveDistance = dragContext.getMouseMoveDistance(event.x, event.y);
if (Math.abs(mouseDownDistance.x) > dragThreshold) {
StripItem item = dragContext.getItem();
if (item != null) {
if (item.getState() == StripItemState.PRESSED) {
item.setState(StripItemState.DRAGGED, false);
item.setOffset(item.getOffset() + (mouseDownDistance.x - mouseMoveDistance.x), false, null);
}
}
}
StripItem item = dragContext.getItem();
if (item.getState() == StripItemState.DRAGGED) {
int newOffset = item.getOffset() + mouseMoveDistance.x;
StripItem[] affectedItems = getAffected(item, mouseMoveDistance.x);
if (mouseMoveDistance.x < 0) {
for (StripItem affectedItem : affectedItems) {
int destination = shouldAlignBefore(affectedItem);
int offset = destination - affectedItem.getBounds().x;
affectedItem.setOffset(offset, true, null);
}
}
if (mouseMoveDistance.x > 0) {
for (StripItem affectedItem : affectedItems) {
int destination = shouldAlignAfter(affectedItem);
int offset = destination - affectedItem.getBounds().x;
affectedItem.setOffset(offset, true, null);
}
}
item.setOffset(newOffset, false, null);
}
dragContext.setMouseMoveLocation(event.x, event.y);
}
}
protected void onMouseUp(Event event) {
if (event.button == 1) {
if (dragContext != null) {
StripItem item = dragContext.getItem();
if (item.getState() == StripItemState.PRESSED) {
if (dragContext.getMouseDownState() == StripItemState.SELECTED) {
if (isMultiSelection()) {
item.setState(StripItemState.FOCUSED, false);
} else {
item.setState(StripItemState.SELECTED, false);
}
} else {
item.setState(StripItemState.SELECTED, false);
}
if (!isMultiSelection()) {
Set<StripItem> otherSelectedItems = getItemSet(StripItemState.SELECTED);
otherSelectedItems.remove(item);
for (StripItem otherSelectedItem : otherSelectedItems) {
otherSelectedItem.setState(StripItemState.DEFAULT, true);
}
}
} else if (item.getState() == StripItemState.DRAGGED) {
if (dragContext.getMouseDownState() == StripItemState.SELECTED) {
item.setState(StripItemState.SELECTED, false);
} else {
item.setState(StripItemState.DEFAULT, false);
}
int after = getAfter(item);
int offset = after - item.getBounds().x;
item.setOffset(offset, true, new Runnable() {
@Override
public void run() {
reorderItems();
}
});
}
dragContext = null;
}
}
}
private int getAfter(StripItem item) {
Rectangle bounds = item.getBounds();
bounds.x += item.getOffset();
for (int i = items.size() - 1; i >= 0; i--) {
StripItem anItem = items.get(i);
Rectangle anItemBounds = anItem.getBounds();
anItemBounds.x += anItem.getOffset();
if (anItemBounds.x < bounds.x) {
return anItemBounds.x + anItemBounds.width + itemSpacing;
}
}
return marginLeft;
}
protected void reorderItems() {
Comparator<StripItem> comparator = new Comparator<StripItem>() {
@Override
public int compare(StripItem item, StripItem otherItem) {
return (item.getBounds().x + item.getOffset()) - (otherItem.getBounds().x + otherItem.getOffset());
}
};
Collections.sort(items, comparator);
for (StripItem item : items) {
item.setOffset(0, false, null);
}
}
protected void onDispose(Event event) {
StripItem[] items = getItems();
for (StripItem item : items) {
item.dispose();
}
}
protected void onAddItem(StripItem item, int index) {
if (index < 0 || index > items.size()) {
index = items.size();
}
items.add(index, item);
}
protected void onRemoveItem(StripItem item) {
items.remove(item);
}
protected void onFocusItem(StripItem item) {
StripItem lastFocusedItem = getItem(StripItemState.FOCUSED);
if (lastFocusedItem != null) {
if (!lastFocusedItem.equals(item)) {
lastFocusedItem.setState(StripItemState.DEFAULT, true);
}
}
if (item != null) {
if (!item.equals(lastFocusedItem)) {
if (item.getState() != StripItemState.SELECTED) {
if (getItem(StripItemState.PRESSED) == null) {
if (getItem(StripItemState.DRAGGED) == null) {
item.setState(StripItemState.FOCUSED, true);
}
}
}
}
}
}
protected Set<StripItem> getItemSet(StripItemState state) {
Set<StripItem> itemSet = new HashSet<StripItem>();
for (StripItem item : items) {
if (item.getState().equals(state)) {
itemSet.add(item);
}
}
return itemSet;
}
protected Point computeInternalSize() {
Point size = new Point(0, 0);
for (StripItem item : items) {
Rectangle bounds = item.getBounds();
size.x += bounds.width;
size.y = Math.max(size.y, bounds.height);
}
size.y += marginTop;
size.y += marginBottom;
size.x += marginLeft;
size.x += marginRight;
size.x += itemSpacing * (items.size() - 1);
return size;
}
private StripItem[] getAffectedItems(StripItem dragItem, int oldOffset, int newOffset) {
Set<StripItem> affectedItems = new HashSet<StripItem>();
Rectangle dragItemBounds = dragItem.getBounds();
dragItemBounds.x += dragItem.getOffset();
for (StripItem otherItem : items) {
if (otherItem.equals(dragItem)) {
continue;
}
Rectangle otherItemBounds = otherItem.getBounds();
otherItemBounds.x += otherItem.getOffset();
if (newOffset > oldOffset) {
if (dragItemBounds.x < otherItemBounds.x) {
if (dragItemBounds.x + dragItemBounds.width > otherItemBounds.x + otherItemBounds.width / 2) {
affectedItems.add(otherItem);
}
}
}
if (newOffset < oldOffset) {
if (dragItemBounds.x > otherItemBounds.x) {
if (dragItemBounds.x < otherItemBounds.x + otherItemBounds.width / 2) {
affectedItems.add(otherItem);
}
}
}
}
return affectedItems.toArray(new StripItem[affectedItems.size()]);
}
private StripItem[] getAffected(StripItem item, int delta) {
Set<StripItem> affected = new HashSet<StripItem>();
Rectangle bounds = item.getBounds();
bounds.x += item.getOffset();
for (StripItem otherStripItem : items) {
if (otherStripItem.equals(item)) {
continue;
}
Rectangle otherBounds = otherStripItem.getBounds();
otherBounds.x += otherStripItem.getOffset();
if (delta > 0) {
if (bounds.x < otherBounds.x) {
if (bounds.x + delta + bounds.width > otherBounds.x + otherBounds.width / 2) {
affected.add(otherStripItem);
}
}
}
if (delta < 0) {
if (bounds.x > otherBounds.x) {
if (bounds.x + delta < otherBounds.x + otherBounds.width / 2) {
affected.add(otherStripItem);
}
}
}
}
return affected.toArray(new StripItem[affected.size()]);
}
private int shouldAlignBefore(StripItem item) {
Point size = computeInternalSize();
int right = size.x - marginRight;
for (int i = items.size() - 1; i >= 0; i--) {
StripItem anItem = items.get(i);
if (anItem.getState() == StripItemState.DRAGGED) {
continue;
}
if (anItem.equals(item)) {
return right - item.getBounds().width;
}
right -= anItem.getBounds().width;
right -= itemSpacing;
}
return 0;
}
private int shouldAlignAfter(StripItem item) {
int left = marginLeft;
for (int i = 0; i < items.size(); i++) {
StripItem anItem = items.get(i);
if (anItem.getState() == StripItemState.DRAGGED) {
continue;
}
if (anItem.equals(item)) {
return left;
}
left += anItem.getBounds().width;
left += itemSpacing;
}
return 0;
}
public static void main(String[] args) {
Display display = Display.getDefault();
TimingSource timingSource = new SWTTimingSource(16, TimeUnit.MILLISECONDS, display);
timingSource.init();
Animator.setDefaultTimingSource(timingSource);
Shell shell = new Shell(display);
StripBar bar = new StripBar(shell, SWT.DOUBLE_BUFFERED | SWT.BORDER);
StripItem item1 = new StripItem(bar, SWT.NONE);
item1.setText("Item 1");
StripItem item2 = new StripItem(bar, SWT.NONE);
item2.setImage(UIImageResource.T.addContact16());
StripItem item3 = new StripItem(bar, SWT.NONE);
StripItem item0 = new StripItem(bar, SWT.NONE, 0);
item0.setText("Item 0");
item0.setImage(UIImageResource.T.about16());
GridLayout layout = new GridLayout();
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
layout.marginWidth = 0;
layout.marginHeight = 0;
shell.setLayout(layout);
GridData data = new GridData();
data.horizontalAlignment = SWT.FILL;
data.verticalAlignment = SWT.FILL;
data.grabExcessHorizontalSpace = true;
bar.setLayoutData(data);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}