/*******************************************************************************
* Copyright (c) 2015 Fabio Zadrozny and others.
* 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:
* Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.e4.ui.css.swt.dom;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.Platform;
import org.eclipse.e4.ui.css.core.engine.CSSEngine;
import org.eclipse.e4.ui.internal.css.swt.dom.AbstractControlSelectionEraseListener;
import org.eclipse.e4.ui.internal.css.swt.dom.ControlSelectedColorCustomization;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
public class TreeElement extends ControlElement
implements ISelectionBackgroundCustomizationElement, IHeaderCustomizationElement {
private static boolean showedUnsupportedWarning = false;
private final static String TREE_ARROWS_FOREGROUND_COLOR = "org.eclipse.e4.ui.css.swt.treeArrowsForegroundColor"; //$NON-NLS-1$
private final static String TREE_ARROWS_MODE = "org.eclipse.e4.ui.css.swt.treeArrowsMode"; //$NON-NLS-1$
private final static String TREE_ARROWS_MODE_TRIANGLE = "triangle"; //$NON-NLS-1$
private final static String TREE_ARROWS_MODE_SQUARE = "square"; //$NON-NLS-1$
private static abstract class TreeItemPaintListener implements Listener {
@Override
public void handleEvent(Event event) {
Widget item = event.item;
if (!(item instanceof TreeItem)) {
return;
}
TreeItem treeItem = (TreeItem) item;
if (treeItem.getItemCount() == 0) {
return;
}
Tree parent = treeItem.getParent();
boolean isCheckTree = (parent.getStyle() & SWT.CHECK) != 0;
Object data = parent.getData(TREE_ARROWS_FOREGROUND_COLOR);
if (!(data instanceof Color)) {
// If color is not set, bail out.
return;
}
Color foreground = (Color) data;
Color background = null;
// The arrow drawing needs to consider the selected/hot background.
if ((event.detail & SWT.SELECTED) != 0) {
background = ControlSelectedColorCustomization.getSelectionBackgroundColor(parent);
} else if ((event.detail & SWT.HOT) != 0) {
background = ControlSelectedColorCustomization.getHotBackgroundColor(parent);
}
if(background == null){
background = parent.getBackground();
}
GC gc = event.gc;
gc.setForeground(foreground);
if (background != null) {
gc.setBackground(background);
}
// If it's a check tree, we should consider the space that the
// checkbox takes.
int baseX = isCheckTree ? (event.x - (16 * 2)) : (event.x - 16);
// Many windows-only magic numbers here to erase the previous
// arrow drawing.
gc.fillRectangle(baseX, event.y + 4, 10, 11);
// Draw the new arrow for the proper mode.
draw(treeItem, foreground, background, event, baseX);
}
protected abstract void draw(TreeItem treeItem, Color foreground, Color background, Event event, int baseX);
}
/**
* A painter to draw squares as tree arrows.
*/
private static final TreeItemPaintListener treeItemSquaresPaintListener = new TreeItemPaintListener() {
@Override
protected void draw(TreeItem treeItem, Color foreground, Color background, Event event, int baseX) {
GC gc = event.gc;
// Many windows-only magic numbers here (the code below creates a
// square with + or - depending on whether the item is collapsed or
// expanded).
int w = 9;
int h = 9;
int x = baseX;
int y = event.y + 4;
int halfH = h / 2;
gc.drawRectangle(x + 1, y + 1, w - 1, h - 1);
gc.drawLine(x + 3, y + halfH + 1, x + w - 2, y + halfH + 1);
if (!treeItem.getExpanded()) {
int halfW = w / 2;
gc.drawLine(x + halfW + 1, y + 3, x + halfW + 1, y + h - 2);
}
event.detail &= ~SWT.BACKGROUND;
}
};
/**
* A painter to draw triangles as tree arrows.
*/
private static final TreeItemPaintListener treeItemArrowsPaintListener = new TreeItemPaintListener() {
@Override
protected void draw(TreeItem treeItem, Color foreground, Color background, Event event, int baseX) {
GC gc = event.gc;
int w = 9;
int h = 9;
int x = baseX;
int y = event.y + 4;
int halfH = h / 2;
// Many windows-only magic numbers here (the code draws a triangle
// with the same coordinates used by windows -- with different
// rotations depending on the expanded state).
if (!treeItem.getExpanded()) {
// Draws an open triangle if closed
int px0 = x + 1;
int py0 = y + 1;
int py1 = y + halfH + 1;
int px1 = x + (w / 2) + 1;
int py2 = y + h;
gc.drawLine(px0, py0, px0, py2);
gc.drawLine(px0, py0, px1, py1);
gc.drawLine(px0, py2, px1, py1);
} else {
// Draws a closed triangle if closed
int px0 = x;
int py0 = y;
int px1 = x + w - 2;
int py2 = y + h - 2;
gc.setBackground(foreground);
gc.fillPolygon(new int[] { px1, py0, px1, py2, px0, py2, px1, py0 });
gc.setBackground(background);
}
event.detail &= ~SWT.BACKGROUND;
}
};
private static class TreeControlSelectionEraseListener extends AbstractControlSelectionEraseListener {
@Override
protected void fixEventDetail(Control control, Event event) {
event.detail &= ~SWT.SELECTED;
}
@Override
protected int getNumberOfColumns(Control control) {
return ((Tree) control).getColumnCount();
}
}
// Helper to delegate methods related to the control background
// customization
private final ControlSelectedColorCustomization fControlSelectedColorCustomization;
public TreeElement(Tree tree, CSSEngine engine) {
super(tree, engine);
fControlSelectedColorCustomization = new ControlSelectedColorCustomization(tree,
new TreeControlSelectionEraseListener());
}
public Tree getTree() {
return (Tree) getNativeWidget();
}
@Override
public void reset() {
setTreeArrowsForegroundColor(null);
setHeaderColor(null);
setHeaderBackgroundColor(null);
super.reset();
}
/**
* Actually sets the paint listener.
*
* @param color
* the foreground color to be used (if null removes the paint
* listener).
*/
private void setPaintListener(Color color) {
if (Platform.OS_WIN32.equals(Platform.getOS())) {
Tree tree = getTree();
// Make sure we don't add the listener twice.
tree.removeListener(SWT.PaintItem, treeItemSquaresPaintListener);
tree.removeListener(SWT.PaintItem, treeItemArrowsPaintListener);
if (color != null) {
String treeArrowsMode = getTreeArrowsMode();
if (TREE_ARROWS_MODE_TRIANGLE.equals(treeArrowsMode)) {
tree.addListener(SWT.PaintItem, treeItemArrowsPaintListener);
} else if (TREE_ARROWS_MODE_SQUARE.equals(treeArrowsMode)) {
tree.addListener(SWT.PaintItem, treeItemSquaresPaintListener);
} else if (!showedUnsupportedWarning) {
System.err.println("Unsupported swt-tree-arrow-mode: " + treeArrowsMode);
showedUnsupportedWarning = true;
}
}
} else if (!showedUnsupportedWarning) {
System.err.println("swt-tree-arrow-mode and swt-tree-arrow-color are not supported on this platform");
showedUnsupportedWarning = true;
}
}
/**
* Adds a custom paint listener which replaces the original tree arrows and
* draws new ones (based on the state of the TreeItem) if a color is passed
* (if null is passed, returns to the standard behavior).
*
* @param color
* The foreground color to be used to paint the items. May be
* null (in which case we stop our custom painter from painting
* the tree items).
*/
public void setTreeArrowsForegroundColor(Color color) {
Tree tree = getTree();
tree.setData(TREE_ARROWS_FOREGROUND_COLOR, color);
setPaintListener(color);
}
/**
* @return the color to be used to draw the tree arrows foreground.
*/
public Color getTreeArrowsForegroundColor() {
Tree tree = getTree();
Object data = tree.getData(TREE_ARROWS_FOREGROUND_COLOR);
if (data instanceof Color) {
return (Color) data;
}
return null;
}
/**
* Sets the way to draw the tree arrows.
*
* @see #TREE_ARROWS_MODE_TRIANGLE
* @see #TREE_ARROWS_MODE_SQUARE
*/
public void setTreeArrowsMode(String arrowsMode) {
Tree tree = getTree();
if (arrowsMode == null) {
tree.setData(TREE_ARROWS_MODE, null);
setPaintListener(getTreeArrowsForegroundColor());
return;
}
Assert.isTrue(TREE_ARROWS_MODE_TRIANGLE.equals(arrowsMode) || TREE_ARROWS_MODE_SQUARE.equals(arrowsMode));
tree.setData(TREE_ARROWS_MODE, arrowsMode);
}
/**
* @return the way to draw the tree arrows.
*
* @see #TREE_ARROWS_MODE_TRIANGLE
* @see #TREE_ARROWS_MODE_SQUARE
*/
public String getTreeArrowsMode() {
Tree tree = getTree();
Object data = tree.getData(TREE_ARROWS_MODE);
if (TREE_ARROWS_MODE_TRIANGLE.equals(data) || TREE_ARROWS_MODE_SQUARE.equals(data)) {
return (String) data;
}
// Default is arrows
return TREE_ARROWS_MODE_TRIANGLE;
}
@Override
public void setSelectionBackgroundColor(Color color) {
this.fControlSelectedColorCustomization.setSelectionBackgroundColor(color);
}
@Override
public Color getSelectionBackgroundColor() {
return this.fControlSelectedColorCustomization.getSelectionBackgroundColor();
}
@Override
public void setSelectionBorderColor(Color color) {
this.fControlSelectedColorCustomization.setSelectionBorderColor(color);
}
@Override
public Color getSelectionBorderColor() {
return this.fControlSelectedColorCustomization.getSelectionBorderColor();
}
@Override
public void setHotBackgroundColor(Color color) {
this.fControlSelectedColorCustomization.setHotBackgroundColor(color);
}
@Override
public Color getHotBackgroundColor() {
return this.fControlSelectedColorCustomization.getHotBackgroundColor();
}
@Override
public void setHotBorderColor(Color color) {
this.fControlSelectedColorCustomization.setHotBorderColor(color);
}
@Override
public Color getHotBorderColor() {
return this.fControlSelectedColorCustomization.getHotBorderColor();
}
@Override
public Color getSelectionForegroundColor() {
return this.fControlSelectedColorCustomization.getSelectionForegroundColor();
}
@Override
public void setSelectionForegroundColor(Color color) {
this.fControlSelectedColorCustomization.setSelectionForegroundColor(color);
}
@Override
public void setHeaderColor(Color color) {
getTree().setHeaderForeground(color);
}
@Override
public void setHeaderBackgroundColor(Color color) {
getTree().setHeaderBackground(color);
}
}