blob: 67f39bbbfc64ca99f1943e3e710f3c50d9028073 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 Exyte
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Yuri Strot - initial API and Implementation
*******************************************************************************/
package org.eclipse.ui.glance.controls.items;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.glance.sources.ColorManager;
import org.eclipse.ui.glance.sources.ITextSourceListener;
import org.eclipse.ui.glance.utils.TextUtils;
/**
* @author Yuri Strot
*
*/
public class ItemDecorator implements Listener {
public static final int DEFAULT_STYLE = SWT.BACKGROUND | SWT.FOREGROUND | SWT.SELECTED | SWT.HOT;
protected Composite composite;
protected ItemProvider provider;
protected int style;
protected TextLayout textLayout;
protected Map<ItemCell, StyleRange[]> itemToMatches;
protected Map<ItemCell, StyleRange[]> cacheStyles;
protected List<ItemCell> cells;
protected HashSet<ItemCell> cellSet;
protected boolean customPaintingUsed;
private ListenerList<ITextSourceListener> listeners = new ListenerList<>();
public ItemDecorator(Composite composite, ItemProvider provider) {
this(composite, provider, DEFAULT_STYLE);
}
public ItemDecorator(Composite composite, ItemProvider provider, int style) {
this.composite = composite;
this.provider = provider;
this.style = style;
clearStyles();
init();
}
public void addTextSourceListener(ITextSourceListener listener) {
listeners.add(listener);
}
public void removeTextSourceListener(ITextSourceListener listener) {
listeners.remove(listener);
}
public ITextSourceListener[] getListeners() {
Object[] objects = listeners.getListeners();
ITextSourceListener[] listeners = new ITextSourceListener[objects.length];
System.arraycopy(objects, 0, listeners, 0, objects.length);
return listeners;
}
public void blocksChanged(ItemCell[] removed, ItemCell[] added) {
for (ItemCell cell : removed) {
cells.remove(cell);
cellSet.remove(cell);
}
for (ItemCell cell : added) {
cells.add(cell);
cellSet.add(cell);
cell.getItem().addListener(SWT.Dispose, this);
}
for (ITextSourceListener listener : getListeners()) {
listener.blocksChanged(removed, added);
}
}
public void clearStyles() {
itemToMatches = new HashMap<ItemCell, StyleRange[]>();
cacheStyles = new HashMap<ItemCell, StyleRange[]>();
}
public List<ItemCell> getCells() {
return cells;
}
public void setCells(List<ItemCell> cells) {
this.cells = cells;
for (ItemCell cell : cells) {
cell.getItem().addListener(SWT.Dispose, this);
}
cellSet = new HashSet<ItemCell>(cells);
}
public void setStyles(ItemCell cell, StyleRange[] styles) {
if (styles == null)
itemToMatches.remove(cell);
else
itemToMatches.put(cell, styles);
cacheStyles.put(cell, calculateStyles(cell));
}
public void redraw() {
Rectangle rect = composite.getClientArea();
composite.redraw(rect.x, rect.y, rect.width, rect.height, true);
}
public void redraw(ItemCell cell) {
Rectangle rect = provider.getBounds(cell.getItem(), cell.getIndex());
composite.redraw(rect.x, rect.y, rect.width, rect.height, true);
}
protected TextLayout getTextLayout() {
if (textLayout == null) {
int orientation = composite.getStyle() & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT);
textLayout = new TextLayout(composite.getDisplay());
textLayout.setOrientation(orientation);
} else {
textLayout.setText("");
}
return textLayout;
}
protected StyleRange[] getRanges(ItemCell cell) {
StyleRange[] ranges = cacheStyles.get(cell);
if (ranges == null) {
ranges = calculateStyles(cell);
cacheStyles.put(cell, ranges);
}
return ranges;
}
protected StyleRange[] calculateStyles(ItemCell cell) {
StyleRange[] cellStyles = cell.getStyles();
StyleRange[] matchStyles = itemToMatches.get(cell);
if (matchStyles == null || matchStyles.length == 0)
return cellStyles;
if (cellStyles.length == 0)
return matchStyles;
Region region = new Region(0, cell.getLength());
int size = cellStyles.length + matchStyles.length;
TextPresentation presentation = new TextPresentation(region, size);
presentation.replaceStyleRanges(cellStyles);
presentation.mergeStyleRanges(matchStyles);
return TextUtils.getStyles(presentation);
}
@Override
public void handleEvent(Event event) {
switch (event.type) {
case SWT.PaintItem:
paint(event);
break;
case SWT.EraseItem:
erase(event);
break;
case SWT.Dispose:
if (event.widget instanceof Item) {
ItemCell cell = new ItemCell((Item) event.widget, event.index, provider);
blocksChanged(new ItemCell[] { cell }, new ItemCell[0]);
}
break;
}
}
protected void paint(Event event) {
Item item = (Item) event.item;
GC gc = event.gc;
// remember colors to restore the GC later
Color oldForeground = gc.getForeground();
Color oldBackground = gc.getBackground();
Color foreground = provider.getForeground(item, event.index);
if (foreground != null) {
gc.setForeground(foreground);
}
Color background = provider.getBackground(item, event.index);
if (background != null) {
gc.setBackground(background);
}
if (!customPaintingUsed) {
if (!ColorManager.getInstance().isUseNative() && (event.detail & SWT.SELECTED) != 0) {
gc.setBackground(ColorManager.getInstance().getTreeSelectionBg());
gc.setForeground(ColorManager.getInstance().getTreeSelectionFg());
gc.fillRectangle(provider.getBounds(item, event.index));
}
Image image = provider.getImage(item, event.index);
if (image != null) {
Rectangle imageBounds = provider.getImageBounds(item, event.index);
if (imageBounds != null) {
Rectangle bounds = image.getBounds();
// center the image in the given space
int x = imageBounds.x + Math.max(0, (imageBounds.width - bounds.width) / 2);
int y = imageBounds.y + Math.max(0, (imageBounds.height - bounds.height) / 2);
gc.drawImage(image, x, y);
}
}
}
Rectangle textBounds = provider.getTextBounds(item, event.index);
if (textBounds != null) {
TextLayout layout = getTextLayout();
layout.setText(provider.getText(item, event.index));
layout.setFont(provider.getFont(item, event.index));
ItemCell cell = new ItemCell(item, event.index, provider);
if (!cellSet.contains(cell)) {
blocksChanged(new ItemCell[0], new ItemCell[] { cell });
}
StyleRange[] ranges = getRanges(cell);
for (StyleRange range : ranges) {
layout.setStyle(range, range.start, range.start + range.length - 1);
}
Rectangle layoutBounds = layout.getBounds();
int x = textBounds.x;
int avg = (textBounds.height - layoutBounds.height) / 2;
int y = textBounds.y + Math.max(0, avg);
if (customPaintingUsed) {
if (containsMatches(cell)) {
Rectangle bounds = provider.getBounds(item, event.index);
Color color = ColorManager.getInstance().getBackgroundColor();
if ((event.detail & SWT.SELECTED) != 0)
color = ColorManager.getInstance().getSelectedBackgroundColor();
int preAlpha = gc.getAlpha();
gc.setAlpha(100);
gc.setBackground(color);
gc.fillRectangle(bounds);
gc.setAlpha(preAlpha);
}
} else
layout.draw(gc, x, y);
}
gc.setForeground(oldForeground);
gc.setBackground(oldBackground);
}
private boolean containsMatches(ItemCell cell) {
return itemToMatches.get(cell) != null;
}
public void erase(Event event) {
if (customPaintingUsed)
return;
int style = SWT.BACKGROUND | SWT.FOREGROUND;
if (!ColorManager.getInstance().isUseNative()) {
style |= SWT.SELECTED | SWT.HOT;
}
event.detail &= ~style;
}
protected void init() {
// FIXME
customPaintingUsed = isCustomPaintingUsed(composite);
composite.addListener(SWT.EraseItem, this);
composite.addListener(SWT.PaintItem, this);
redraw();
}
private boolean isCustomPaintingUsed(Composite composite) {
Listener[] listeners = composite.getListeners(SWT.PaintItem);
if (listeners.length > 0) {
Listener listener = listeners[listeners.length - 1];
if (!listener.getClass().getName().startsWith("org.eclipse.swt"))
return true;
}
return false;
}
public void dispose() {
if (!disposed) {
clearStyles();
composite.removeListener(SWT.PaintItem, this);
composite.removeListener(SWT.EraseItem, this);
disposed = true;
redraw();
}
}
public boolean isDisposed() {
return disposed;
}
private boolean disposed;
}