/**************************************************************************** * Copyright (c) 2005-2006 Jeremy Dowdall * 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: * Jeremy Dowdall <jeremyd@aspencloud.com> - initial API and implementation *****************************************************************************/ package org.eclipse.nebula.widgets.ctree; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Button; 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.Listener; /** * <p> * NOTE: THIS WIDGET AND ITS API ARE STILL UNDER DEVELOPMENT. THIS IS A PRE-RELEASE ALPHA * VERSION. USERS SHOULD EXPECT API CHANGES IN FUTURE VERSIONS. * </p> */ public class CTreeCell { // private class DeferredListener { // public int eventType; // public Listener handler; // DeferredListener(int eventType, Listener handler) { // this.eventType = eventType; // this.handler = handler; // } // public boolean equals(Object object) { // if(object instanceof DeferredListener) { // DeferredListener handler = (DeferredListener) object; // return (handler.eventType == this.eventType && handler.handler == this.handler); // } // return false; // } // public boolean isType(int type) { // return eventType == type; // } // } private class EventHandler { List[] handlers; boolean add(int eventType, Listener handler) { if(eventType > 0 && handler != null) { if(handlers == null) { handlers = new List[4]; } if(eventType >= handlers.length) { List[] newHandlers = new List[eventType+1]; System.arraycopy(handlers, 0, newHandlers, 0, handlers.length); handlers = newHandlers; } if(handlers[eventType] == null) { handlers[eventType] = new ArrayList(); } if(!handlers[eventType].contains(handler)) { handlers[eventType].add(handler); return true; } } return false; } int[] getEventTypes() { int count = 0; for(int i = 0; i < handlers.length; i++) { if(handlers[i] != null && !handlers[i].isEmpty()) count++; } int[] types = new int[count]; count = 0; for(int i = 0; i < handlers.length; i++) { if(handlers[i] != null && !handlers[i].isEmpty()) types[count++] = i; } return types; } boolean hasHandler(int eventType) { return (eventType > 0 && eventType < handlers.length && handlers[eventType] != null && !handlers[eventType].isEmpty()); } boolean remove(int eventType, Listener handler) { if(handlers != null && eventType > 0 && eventType < handlers.length && handlers[eventType] != null) { return handlers[eventType].remove(handler); } return false; } void sendEvent(Event event) { if(hasHandler(event.type)) { event.data = CTreeCell.this; event.item = CTreeCell.this.item; // TODO: necessary? Listener[] la = (Listener[]) handlers[event.type].toArray(new Listener[handlers[event.type].size()]); for(int i = 0; i < la.length; i++) { la[i].handleEvent(event); } } } } /** * true if the platform is detected as being "carbon" */ public static final boolean carbon = "carbon".equals(SWT.getPlatform()); /** * true if the platform is detected as being "gtk" */ public static final boolean gtk = "gtk".equals(SWT.getPlatform()); /** * true if the platform is detected as being "win32" */ public static final boolean win32 = "win32".equals(SWT.getPlatform()); private static final Color bgOver = gtk ? Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BORDER) : Display.getCurrent().getSystemColor(SWT.COLOR_BLACK); private static final Color bgNorm = gtk ? Display.getCurrent().getSystemColor(SWT.COLOR_WHITE) : Display.getCurrent().getSystemColor(SWT.COLOR_WHITE); private static final Color border = gtk ? Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BORDER) : Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY); // private static final Color fgOver = gtk ? border : // Display.getCurrent().getSystemColor(SWT.COLOR_WHITE); private static final Color fgNorm = gtk ? border : Display.getCurrent().getSystemColor(SWT.COLOR_BLACK); private static final int[] closedPoints = gtk ? new int[] { 2, 0, 7, 5, 2, 10 } : // new int[] { 2,4, 4,4, 4,2, 5,2, 5,4, 7,4, 7,5, 5,5, 5,7, 4,7, 4,5, 2,5 }; new int[] { 2,4, 6,4, 4,4, 4,2, 4,4, 4,6 }; private static final int[] openPoints = gtk ? new int[] { 10, 2, 5, 7, 0, 2 } : new int[] { 2,4, 6,4 };//, 7,5, 2,5 }; private static final int pointsWidth = gtk ? 11 : 8; /** * A recursive utility function used to get every child control of a composite, * including the children of children.<br/> * NOTE: This method will <b>NOT</b> return disposed children. * @param c the composite to start from * @param exclusions a list of controls to be excluded from the return list. If * an item in this list is a composite, then its children will also be excluded. * @return all the children and grandchildren of the given composite */ private static List getControls(Control c, List exclusions) { if(c != null && !c.isDisposed()) { if(exclusions == null) { exclusions = Collections.EMPTY_LIST; } else if(exclusions.contains(c)) { return Collections.EMPTY_LIST; } List l = new ArrayList(); l.add(c); if(c instanceof Composite) { Control[] a = ((Composite) c).getChildren(); for(int i = 0; i < a.length; i++) { if(!a[i].isDisposed() && !exclusions.contains(a[i])) { if(a[i] instanceof Composite) { l.addAll(getControls(a[i], exclusions)); } else { l.add(a[i]); } } } } return l; } else { return Collections.EMPTY_LIST; } } /** * the container to which this cell's item belongs */ protected CTree ctree; /** * the item to which this cell belongs */ protected CTreeItem item; /** * the index of this cell in its item's cell array */ private int index = -1; /** * the style settings for this cell */ protected int style; /** * the check button control, if this is a check cell */ private Button check; /** * the control which is in the client area of this cell, if created */ private Control control; /** * the horizontal alignment to use when positioning the control in the client area, if it was created */ int hAlign = SWT.RIGHT; /** * the vertical alignment to use when positioning the control in the client area, if it was created */ int vAlign = SWT.FILL; /** * the control which is in the child area of this cell, if created */ private Control childControl; /** * indicates whether or not this cell is considered 'open' or 'closed'. * the actual meaning may vary between concrete implementations. */ protected boolean open = false; /** * indicates whether or not this cell is selected, and thereby included in the * container's current selection list. */ protected boolean selected = false; /** * indicates whether or not this cell is visible and should be considered for * painting by the container. */ protected boolean visible = true; /** * indicates whether or not this cell's child area is visible */ protected boolean childVisible = false; /** * indicates whether or not this cell's toggle is visible */ protected boolean toggleVisible = false; /** * indicates whether or not this cell is actually painted to the screen. */ private boolean painted = false; /** * If true, the toggle will not be drawn, but will take up space */ protected boolean ghostToggle = false; /** * indicates whether or not the primary mouse button is down, whether or not it is over this cell. */ protected boolean mouseDown = false; /** * indicates whether or not the mouse is over this cell. */ protected boolean mouseOver = false; /** * indicates whether or not the mouse is over this cell's toggle. */ protected boolean mouseOverToggle = false; /** * An amount of margin to be applied to the left side of the cell. */ public int marginLeft = gtk ? 2 : 1; /** * An amount of margin to be applied to both the left and right sides of the cell. */ public int marginWidth = 0; /** * An amount of margin to be applied to the right side of the cell. */ public int marginRight = 0; /** * An amount of margin to be applied to the top of the cell. */ public int marginTop = gtk ? 0 : 0; /** * An amount of margin to be applied to both the top and bottom of the cell. */ public int marginHeight = gtk ? 4 : 1; /** * An amount of margin to be applied to the bottom of the cell. */ public int marginBottom = 0; /** * sets the width of the toggle */ public int toggleWidth = 16; /** * the bounds of this cell relative to... */ // protected Rectangle bounds; /** * the bounds of this cell the <b>last</b> time it was painted */ private Rectangle bounds; /** * the client area of this cell the <b>last</b> time it was painted */ private Rectangle clientArea; /** * the bounds of this cell's child area relative to the cell's own * origin (bounds.x, bounds.y) * @see #bounds */ // protected Rectangle childBounds; /** * the bounds of this cell's child area the <b>last</b> time it was painted */ // protected Rectangle childBoundsOld; /** * indicates whether or not this cell has a child area. */ private boolean hasChild; /** * the position of the scroll bars the <b>last</b> time this cell was painted */ private Point scrollPos; /** * the bounds for the toggle, relative to... to what? TODO */ private Rectangle toggleBounds; /** * the font to use for any text in this cell. */ private Font font; /** * The color that the cell will actually use for its background. It may be different * from the storedBackground if, for instance, the cell is selected. * @see #storedBackground */ private Color activeBackground; /** * The color that the cell will actually use for its forground. It may be different * from the storedForeground if, for instance, the cell is selected. * @see #storedForeground */ private Color activeForeground; /** * The color that the cell would like to use for its background. * @see #activeBackground */ private Color storedBackground = null; /** * The color that the cell would like to use for its foreground. * @see #activeForeground */ private Color storedForeground = null; private List colorExclusions; private List eventExclusions; private EventHandler ehandler = new EventHandler(); private Listener l = new Listener() { public void handleEvent(Event event) { ehandler.sendEvent(event); } }; private PropertyChangeSupport pcListeners; private boolean isCheck = false; private boolean isChecked = false; private boolean inited = false; private Image[] images; private String text; private int horizontalSpacing = 2; private Rectangle[] iBounds; private Rectangle tBounds; /** * the amount by which the toggle, and thus the rest of the cell, is to be indented */ protected int indent = 0; /** * indicates whether or not this cell is to be drawn as though it were a gridline. * typically only relevant to GTK. */ protected boolean isGridLine = false; private int[] childSpan = new int[] { -1, 1 }; // default setting keeps the child area // within the same column as the title area private int span = 1; protected boolean holdControl = true; private boolean holdChild = true; public CTreeCell(CTreeItem item, int style) { this.ctree = item.ctree; this.item = item; this.style = style; if((style & SWT.CHECK) != 0) { isCheck = true; } } /** * @param eventType * @param handler */ public void addListener(int eventType, Listener handler) { if(ehandler.add(eventType, handler)) { List controls = getEventManagedControls(); for(Iterator iter = controls.iterator(); iter.hasNext(); ) { Control control = (Control) iter.next(); control.addListener(eventType, l); } // if((getStyle() & SWT.DROP_DOWN) != 0 && childArea == null) { // DeferredListener dl = new DeferredListener(eventType, handler); // boolean contains = false; // for(Iterator i = dlisteners.iterator(); i.hasNext(); ) { // if(dl.equals(i.next())) { // contains = true; // break; // } // } // if(!contains) dlisteners.add(dl); // } } } /** * @param listener */ public void addPropertyChangeListener(PropertyChangeListener listener) { if(pcListeners == null) { pcListeners = new PropertyChangeSupport(this); } pcListeners.addPropertyChangeListener(listener); } /** * @param propertyName * @param listener */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { if(pcListeners == null) { pcListeners = new PropertyChangeSupport(this); } pcListeners.addPropertyChangeListener(propertyName, listener); } public void clear() { text = null; images = null; } Point computeChildSize(int wHint, int hHint) { return null; } /** * compute the size of this cell's client area; that is, compute the size * of the area which is not covered by margins, toggles, or checks. * @param wHint * @param hHint * @return a <code>Point</code> representing the computed size */ public Point computeClientSize(int wHint, int hHint) { Point size = new Point(0,0); Point cSize = computeControlSize(wHint, -1); Point tSize = computeTextSize(wHint-cSize.x, -1); size.x = cSize.x + tSize.x; size.y = Math.max(cSize.y, tSize.y); return size; } Point computeControlSize(int wHint, int hHint) { return (control != null) ? control.computeSize(wHint, hHint) : new Point(0,0); } /** * Compute the preferred size of the cell for its current state (open or closed, if applicable) * just the way it would be done in a regular SWT layout. * @param wHint * @param hHint * @return a Point representing the preferred size of the cell */ public Point computeSize(int wHint, int hHint) { if(wHint == 0 || hHint == 0) return new Point(0,0); Point size = new Point(marginLeft+marginWidth+marginWidth+marginRight, marginTop+marginHeight+marginHeight+marginBottom); if(toggleVisible || ghostToggle) size.x += toggleWidth; if(isCheck) { // TODO: check is null unless cell is painted - request from ctree's painted list // Point cSize = check.computeSize(wHint, hHint); // size.x += cSize.x; // size.y = Math.max(cSize.y, size.y); } Image[] images = getImages(); Rectangle iBounds = null; if(images != null && images.length > 0) { iBounds = images[0].getBounds(); for(int i = 1; i < images.length; i++) { Rectangle ib = images[i].getBounds(); iBounds.width += horizontalSpacing + ib.width; iBounds.height = Math.max(iBounds.height, ib.height); } } else { iBounds = new Rectangle(0,0,0,0); } size.x += iBounds.width; size.y = Math.max(iBounds.height, size.y); Point clientSize = computeClientSize(wHint-size.x, hHint-size.y); if(clientSize != null) { size.x += clientSize.x; size.y += clientSize.y; } Point childSize = computeChildSize(wHint-size.x, hHint-size.y); if(childSize != null) { // size.x += (childSize.x + (childBounds.x - bounds.x)); // size.y += (childSize.y + (childBounds.y - bounds.y)); } if(wHint != SWT.DEFAULT) { size.x = Math.min(size.x, wHint); } if(hHint != SWT.DEFAULT) { size.y = Math.min(size.y, hHint); } return size; } protected Point computeTextSize(int wHint, int hHint) { return (text != null && text.length() > 0) ? internalGC().textExtent(text, SWT.DRAW_DELIMITER | SWT.DRAW_TAB | SWT.DRAW_TRANSPARENT) : new Point(0,0); } /** * @param control * @return true if the control is part of this cell, false otherwise */ boolean contains(Control control) { //TODO: contains? return getEventManagedControls().contains(control); } private Button createCheck() { Button b = new Button(ctree.body, SWT.CHECK); b.setBackground(activeBackground); b.setForeground(activeForeground); b.setSelection(isChecked); b.addListener(SWT.FocusIn, new Listener() { public void handleEvent(Event event) { if(SWT.FocusIn == event.type) ctree.setFocus(); } }); b.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { if(SWT.Selection == event.type) { isChecked = ((Button) event.widget).getSelection(); } } }); // TODO set size of check // b.setSize(b.computeSize(-1, -1)); return b; } /** * Create the contents of your custom cell's Child Area here. * @param parent the parent composite of your new control * @return the newly created control (for multiple controls, create them inside * a composite, and return the composite here) */ protected Control createChildControl(Composite parent) { return null; } /** * Create the contents of your custom cell here * @param parent the parent composite of your new control * @return the newly created control (for multiple controls, create them inside * a composite, and return the composite here) */ protected Control createControl(Composite parent) { return null; } void dispose() { if(check != null) { if(!check.isDisposed()) check.dispose(); check = null; } if(control != null) { if(!control.isDisposed()) control.dispose(); control = null; } if(childControl != null) { if(!childControl.isDisposed()) childControl.dispose(); childControl = null; } } /** * @param propertyName * @param oldValue * @param newValue */ protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { if(pcListeners != null) { pcListeners.firePropertyChange(propertyName, oldValue, newValue); } } /** * Returns the active background color. * @return Color * @see #activeBackground * @see #getForeground() * @see #setBackground(Color) */ public Color getBackground() { return activeBackground; } /** * Returns the bounds of this cell, relative to the container's body. * @return Rectangle */ public Rectangle getBounds() { CTreeColumn col = getColumn(); return new Rectangle( col.getLeft(), item.getTop(), col.getWidth(), item.getHeight()); } /** * Returns the stored background color. * @return Color * @see #storedBackground */ public Color getCellBackground() { return storedBackground; } /** * Returns the stored foreground color. * @return Color * @see #storedForeground */ public Color getCellForeground() { return storedForeground; } /** * Returns whether or not this cell is checked. If the cell is not of * style SWT.CHECK, false is returned. * @return boolean representing the check state of the cell. */ public boolean getChecked() { return isChecked; } /** * Get information on if and how the child area of this CTableTree will span * the columns. * * @return an int[] with a length of 2: int[0] represents the starting * column, and int[1] represents the number of columns, from the * start, to span. * @see #setChildSpan(int, int) */ public int[] getChildSpan() { return childSpan; } /** * Get the client area of this cell. This is the area * where controls can be placed and painting can occur by subclasses. * ONLY VALID WHEN bounds IS VALID * @return Rectangle */ public Rectangle getClientArea() { return clientArea; } /** * Get a list containing all of the controls whose colors (foreground and background) * are managed by this cell. * @return List */ protected List getColorManagedControls() { // TODO add child controls List l = new ArrayList(getControls(control, colorExclusions)); if(check != null) l.add(check); return l; } /** * Get a list containing all of the controls whose events (such as mouse and keyboard) * are managed by this cell. * @return List */ protected List getEventManagedControls() { // TODO add child controls List l = new ArrayList(getControls(control, eventExclusions)); if(check != null) l.add(check); return l; } /** * Get the font in use by this cell. * @return Font */ public Font getFont() { return (font == null || font.isDisposed()) ? null : font; } /** * Returns the active foreground color. * @return Color * @see #activeForeground * @see #getBackground() * @see #setForeground(Color) */ public Color getForeground() { return activeForeground; } public Image getImage() { if(images.length > 0) return images[0]; return null; } public Image[] getImages() { return images == null ? new Image[0] : images; } public int getIndent() { return indent; } public Point getLocation() { Rectangle r = getBounds(); return new Point(r.x, r.y); } public boolean getPainted() { return painted; } public Point getSize() { return new Point(getWidth(),getHeight()); } public int getIndex() { if(index < 0) { index = item.getCellIndex(this); } return index; } public int getHeight() { return item.getHeight(); } public int getWidth() { if(span > 1) { CTreeColumn c1 = ctree.getColumn(getIndex()); CTreeColumn c2 = ctree.getColumn(getIndex()+span); return c2.getRight() - c1.getLeft(); } else { return ctree.getColumn(getIndex()).getWidth(); } } public CTreeColumn getColumn() { return ctree.getColumn(getIndex()); } public int getStyle() { return style; } public String getText() { return (text == null) ? "" : text; } boolean isToggle(int x, int y) { return toggleVisible && toggleBounds.contains(x,y); } boolean getToggleVisible() { return toggleVisible; } /** * Give the Item a chance to handle the mouse event * @param event the Event * @param selectionActive */ protected void handleMouseEvent(Event event, boolean selectionActive) { // switch(event.type) { // case SWT.MouseDown: // Point pt = new Point(event.x, event.y); // boolean tmp = getBounds().contains(pt); // if(tmp != mouseOver) { // mouseOver = tmp; // } // if(!mouseOver && mouseDown) { // mouseDown = false; // } // tmp = toggleVisible && toggleBounds.contains(pt); // if(tmp != mouseOverToggle) { // mouseOverToggle = tmp; // } // break; // } } void initialize() { toggleBounds = new Rectangle(0,0,0,0); iBounds = new Rectangle[0]; tBounds = new Rectangle(0,0,0,0); if((style & SWT.DROP_DOWN) != 0) { hasChild = true; setChildVisible(true); } else { hasChild = false; } if((style & SWT.TOGGLE) != 0) { setToggleVisible(true, true); } if(isCheckCell()) setCheck(true); if(isTreeCell()) { if(((CTreeItem) item).hasParentItem()) { setIndent(((CTreeItem) item).getParentIndent() + ctree.getTreeIndent()); } setToggleVisible(((CTreeItem) item).hasItems(), true); } inited = true; } protected GC internalGC() { return ctree.internalGC; } public boolean isCheckCell() { return ((CTreeItem) item).getCheckCell() == this; } public boolean isOpen() { return open; } public boolean isSelected() { return selected; } public boolean isTreeCell() { return ((CTreeItem) item).getTreeCell() == this; } protected void layout() { } protected void layout(Control control) { Rectangle area = getClientArea(); Point size = control.computeSize(-1, -1); size.x = (hAlign == SWT.FILL) ? area.width : Math.min(area.width, size.x); size.y = (vAlign == SWT.FILL) ? item.getHeight() : Math.min(area.height, size.y); control.setSize(size.x, size.y); locate(control); } private void layout(int eventType) { ctree.layout(eventType, this); } private void layoutCell() { int x = bounds.x + marginLeft + marginWidth + indent; int y = bounds.y + marginTop + marginHeight; int height = item.getHeight() - (marginHeight + marginTop + marginBottom + marginHeight); // TOGGLE if(ghostToggle || toggleVisible) { toggleBounds.x = x; toggleBounds.y = y; toggleBounds.width = Math.min(bounds.width, toggleWidth); toggleBounds.height = height; x += toggleBounds.width; } // CHECK if(isCheck) { check.setBounds( x, item.getTop(), check.computeSize(-1, -1).x, height ); x += check.getSize().x + horizontalSpacing; } // IMAGES if(images == null) { iBounds = new Rectangle[0]; } else { iBounds = new Rectangle[images.length]; for(int i = 0; i < iBounds.length; i++) { iBounds[i] = (images[i] != null && !images[i].isDisposed()) ? images[i].getBounds() : new Rectangle(0,0,1,1); } } for(int i = 0; i < iBounds.length; i++) { iBounds[i].x = x; iBounds[i].y = y; x += iBounds[i].width + horizontalSpacing; } // CLIENT_AREA clientArea = new Rectangle( x, y, bounds.x + bounds.width - x - marginRight - marginWidth, height ); // CONTROL if(control != null) { layout(control); x += control.getSize().x + horizontalSpacing; } // TEXT int width = (control != null) ? clientArea.width-control.getSize().x-horizontalSpacing : clientArea.width; Point tSize = computeTextSize(width, height); tBounds.width = tSize.x; tBounds.height = tSize.y; if((style & SWT.CENTER) != 0) { tBounds.x = clientArea.x+((width-tBounds.width)/2); } else if((style & SWT.RIGHT) != 0) { tBounds.x = clientArea.x+width-tBounds.width; } else { // defaults to LEFT tBounds.x = clientArea.x; } if((open) || (style & SWT.TOP) != 0) { tBounds.y = clientArea.y; } else if((style & SWT.BOTTOM) != 0) { tBounds.y = clientArea.y + clientArea.height - tBounds.height; } else { // defaults to CENTER tBounds.y = clientArea.y + (clientArea.height-tBounds.height)/2; } } protected void layoutChild() {} protected void layoutChild(Control childControl) {} protected void locate(Control control) { Rectangle area = getClientArea(); Point loc = new Point(area.x, item.getTop()); Point size = control.getSize(); if(hAlign == SWT.RIGHT) loc.x += (area.width-size.x); else if(hAlign == SWT.CENTER) loc.x += ((area.width-size.x)/2); // if(vAlign == SWT.BOTTOM) loc.y += (area.height-size.y); // else if(vAlign == SWT.CENTER) loc.y += ((area.height-size.y)/2); control.setLocation(loc); } // protected void locateCheck(Button check) { // Point scroll = ctree.getScrollPosition(); // Rectangle area = getClientArea(); // check.setLocation( // getColumn().getLeft()+area.x-scroll.x, // item.getTop()+-scroll.y+(item.getHeight()-check.computeSize(-1, -1).y)/2 // ); // } // protected abstract void internalLocateChild(Control control) {} protected void locateChild() {} /** * @param gc the GC upon which the cell will be painted * @param ebounds the bounding rectangle of the Container's paint event * @return boolean true if there is custom painting, false otherwise */ void paint(GC gc, Rectangle ebounds) { updateColors(); boolean locate = needsLocate(); boolean layout = needsLayout(); bounds = getBounds(); if(locate) { // TODO: locate currently uses slower layout() layoutCell(); layout(); } if(layout) { layoutCell(); layout(); } else { if(control != null) layout(control); } if(gtk) { paintCell(gc, new Point(-ebounds.x,-ebounds.y)); } else { // TODO !gtk offset in paint routine } } void paintCell(GC gc, Point offset) { if(activeBackground != null) gc.setBackground(activeBackground); if(activeForeground != null) gc.setForeground(activeForeground); // background gc.fillRectangle( bounds.x+offset.x, bounds.y+offset.y, bounds.width, bounds.height ); // images for(int i = 0; i < iBounds.length; i++) { if(!images[i].isDisposed()) { gc.drawImage(images[i], offset.x+iBounds[i].x, offset.y+iBounds[i].y); } } // text if(getText().length() > 0) { Font bf = gc.getFont(); if(getFont() != null) gc.setFont(getFont()); gc.drawText(getText(), offset.x+tBounds.x, offset.y+tBounds.y); if(getFont() != null) gc.setFont(bf); } if(((CTreeItem) item).getTreeCell() == this) { paintChildLines(gc, offset); } // toggle (it changes the colors again so paint it last...) if(toggleVisible) { paintToggle(gc, offset); } } private boolean needsLayout() { // TODO: if text changed // TODO: if images changed // TODO: if ??? changed return ( bounds == null || bounds.height != item.getHeight() || bounds.width != getColumn().getWidth() ); } private boolean needsLocate() { // TODO: scroll position for when !gtk return ( bounds == null || bounds.y != item.getTop() || bounds.x != getColumn().getLeft() ); } public boolean paintClientArea(GC gc, Rectangle area) { // to be implemented by subclasses return false; } protected Display getDisplay() { return ctree.getDisplay(); } private void paintChildLines(GC gc, Point offset) { if(win32) { int gline = ctree.getGridLineWidth(); Rectangle ibounds = item.getBounds(); ibounds.y++; Rectangle tbounds = getBounds(); tbounds.y++; int x1 = 0, x2 = 0, y1 = 0, y2 = 0; int x = toggleBounds.x + (toggleBounds.width/2) - offset.x; int y = tbounds.y + (tbounds.height/2) - offset.y; gc.setForeground(ctree.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); gc.setLineDash(new int[] { 1, 1 }); x1 = x; x2 = x+toggleBounds.width/2; y1 = y; y2 = y; if(gline % 2 == 0) { if(y1 % 2 == 1) y1 -= 1; if(y2 % 2 == 1) y2 -= 1; } gc.drawLine(x1, y1, x2, y2); CTreeItem it = (CTreeItem) item; int index = Arrays.asList( it.hasParentItem() ? it.getParentItem().getItems() : ctree.getItems() ).indexOf(it); int count = it.hasParentItem() ? it.getParentItem().getItemCount() : ctree.getItemCount(); if(index > 0 || it.hasParentItem()) { x1 = x; x2 = x; y1 = ibounds.y - offset.y; y2 = y; if(gline % 2 == 0) { if(y1 % 2 == 1) y1 -= 1; if(y2 % 2 == 1) y2 -= 1; } gc.drawLine(x1, y1, x2, y2); } if(index < count - 1) { x1 = x; x2 = x; y1 = y; y2 = (it.hasParentItem() ? it.getParentItem().getItem(index+1).getCellBounds()[0].y : ctree.getItem(index+1).getCellBounds()[0].y) - offset.y; if(gline % 2 == 0) { if(y1 % 2 == 1) y1 -= 1; if(y2 % 2 == 1) y2 -= 1; } gc.drawLine(x1, y1, x2, y2); } x1 = x; x2 = x; y1 = ibounds.y - offset.y; y2 = ibounds.y+ibounds.height - offset.y; if(gline % 2 == 0) { if(y1 % 2 == 1) y1 -= 1; if(y2 % 2 == 1) y2 -= 1; } while(it.hasParentItem()) { x1 = x2 -= ((CTree) ctree).getTreeIndent(); it = it.getParentItem(); index = Arrays.asList( it.hasParentItem() ? it.getParentItem().getItems() : ctree.getItems() ).indexOf(it); count = it.hasParentItem() ? it.getParentItem().getItemCount() : ctree.getItemCount(); if(index < count - 1) { gc.drawLine(x1, y1, x2, y2); } } if(open && ((CTreeItem) item).hasItems()) { x1 = x2 = x + ((CTree) ctree).getTreeIndent(); y1 = tbounds.y + tbounds.height - offset.y; y2 = ibounds.y + ibounds.height - offset.y; if(gline % 2 == 0) { if(y1 % 2 == 1) y1 -= 1; if(y2 % 2 == 1) y2 -= 1; } gc.drawLine(x1, y1, x2, y2); } gc.setLineDash(null); } } private void paintToggle(GC gc, Point offset) { double x = toggleBounds.x + ((toggleBounds.width - pointsWidth) / 2) + offset.x; double y = toggleBounds.y + ((toggleBounds.height - pointsWidth) / 2) + offset.y; int[] data = open ? openPoints : closedPoints; int[] pts = new int[data.length]; for (int i = 0; i < data.length; i += 2) { pts[i] = data[i] + (int) x; pts[i+1] = data[i+1] + (int) y; } if(gtk){ gc.setForeground(border); gc.setBackground((mouseOverToggle && !mouseDown) ? bgOver : bgNorm); gc.setAntialias(SWT.ON); gc.fillPolygon(pts); gc.drawPolygon(pts); gc.setAdvanced(false); } else if(carbon) { // TODO: carbon toggle gc.setBackground(ctree.getDisplay().getSystemColor(SWT.COLOR_GRAY)); gc.setAntialias(SWT.ON); gc.fillPolygon(pts); gc.setAdvanced(false); } else { gc.setBackground(bgNorm); gc.fillRoundRectangle( (int) x, (int) y, pointsWidth, pointsWidth, 3, 3); Color color = new Color(ctree.getDisplay(), 223, 229, 234); gc.setForeground(color); gc.drawLine( (int) x, (int) y + pointsWidth-2, (int) x + pointsWidth, (int) y + pointsWidth-2 ); gc.drawLine( (int) x + pointsWidth-1, (int) y, (int) x + pointsWidth-1, (int) y + pointsWidth-2 ); gc.drawLine( (int) x + pointsWidth-2, (int) y + pointsWidth-3, (int) x + pointsWidth-2, (int) y + pointsWidth-3 ); color.dispose(); color = new Color(ctree.getDisplay(), 196, 206, 216); gc.setForeground(color); color.dispose(); gc.drawLine( (int) x, (int) y + pointsWidth-1, (int) x + pointsWidth, (int) y + pointsWidth-1 ); gc.setForeground(border); gc.drawRoundRectangle( (int) x, (int) y, pointsWidth, pointsWidth, 3, 3); gc.setForeground(fgNorm); gc.drawPolyline(pts); } } public void redraw() { if(painted) ctree.redraw(this); } public void removeListener(int eventType, Listener handler) { if(ehandler.remove(eventType, handler)) { List controls = getEventManagedControls(); for(Iterator iter = controls.iterator(); iter.hasNext(); ) { Control control = (Control) iter.next(); control.removeListener(eventType, handler); } // if((getStyle() & SWT.DROP_DOWN) != 0) { // DeferredListener h = new DeferredListener(eventType, handler); // for(Iterator i = dlisteners.iterator(); i.hasNext(); ) { // if(h.equals(i.next())) i.remove(); // } // } } } public void removePropertyChangeListener(PropertyChangeListener listener) { if(pcListeners != null) { pcListeners.removePropertyChangeListener(listener); } } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { if(pcListeners != null) { pcListeners.removePropertyChangeListener(propertyName, listener); } } protected void restoreState(Map memento) { // base implementation so subclass do not need to override } protected Map saveState() { return Collections.EMPTY_MAP; } public void setBackground(Color color) { storedBackground = color; updateColors(); } // protected void setBounds(int x, int y, int width, int height) { // bounds.x = x; // bounds.y = y; // bounds.width = width; // bounds.height = height; // snap(bounds); // } // public Control getControl() { return control; } // protected void setBounds(Rectangle bounds) { // setBounds(bounds.x,bounds.y,bounds.width,bounds.height); // } public void setCheck(boolean check) { if(isCheck != check) { isCheck = check; if(!check) { isChecked = false; if(this.check != null) { this.check.dispose(); this.check = null; } } } } public void setChecked(boolean checked) { if(isCheck) isChecked = checked; } /** * Set which columns the Child Area of this CTableTreeCell will span.<br /> * The default setting is: start == -1 and len == 1. To span the entire * CTableTree (all columns), then use <code>setChildSpan(0, -1)</code>. * * @param start * the column in which the child area will begin. A value of -1 * indicates that the child area should begin in the same column * as its title area (same cell). * @param len * how many columns, starting with the one specified by 'start', * the child area will span. A value of '-1' indicates that the * child area should span all the way to the end of the last * column. * @see #getChildSpan() */ public void setChildSpan(int start, int len) { childSpan[0] = start; childSpan[1] = len; } public void setChildVisible(boolean visible) { childVisible = visible; updateVisibility(); } protected void setControlLayoutData(int horizontalAlignment, int verticalAlignment) { hAlign = horizontalAlignment; vAlign = verticalAlignment; } protected void setCursor(int id) { ctree.setCursor(ctree.getDisplay().getSystemCursor(id)); } protected void setExclusions(Control exclude) { colorExclusions = eventExclusions = Collections.singletonList(exclude); } protected void setExclusions(List colors, List events) { colorExclusions = colors; eventExclusions = events; } public boolean setFocus() { // TODO setFocus not yet implemented return false; } public void setFont(Font font) { this.font = font; // TODO: redraw(); } public void setForeground(Color color) { storedForeground = color; updateColors(); } public void setGridLine(boolean isGridLine) { if(this.isGridLine != isGridLine) { this.isGridLine = isGridLine; updateColors(); } } public void setImage(Image image) { if(image == null) images = new Image[0]; else images = new Image[] { image }; } // private boolean isClear() { // return (text == null || text.length() == 0) && (images == null || images.length == 0); // } public void setImages(Image[] images) { if(images == null) { this.images = new Image[0]; } else { boolean doit = true; for(int i = 0; i < images.length; i++) { if(images[i] == null || images[i].isDisposed()) doit = false; } if(doit) this.images = images; } redraw(); } public void setIndent(int indent) { this.indent = indent; } /** * Requests the cell to either open or close * <p>The base implementation creates the Child Area the first time * setOpen(true) is called (if, of course, the cell was created with * the SWT.DROP_DOWN style).</p> * <p>What exactly open refers to can be overridden by subclasses</p> * @param open if true, the cell will open, otherwise it will close */ public void setOpen(boolean open) { if(this.open != open) { this.open = open; if(hasChild) { // TODO: setOpen - childArea updateVisibility(); } if(!isTreeCell()) layout(open ? SWT.Expand : SWT.Collapse); } } void setPainted(boolean painted) { if(this.painted != painted) { this.painted = painted; if(painted) { if(!inited) { initialize(); } if(isCheck && check == null) check = createCheck(); if(control == null) { control = createControl(ctree.body); } else { control.setVisible(true); } } else { bounds = null; scrollPos = null; if(check != null) { check.dispose(); check = null; } if(control != null) { if(holdControl) { control.setVisible(false); } else { control.dispose(); control = null; } } if(childControl != null) { if(holdChild) { childControl.setVisible(painted); } else { childControl.dispose(); childControl = null; } } } } } /** * Set the selection state of this cell. * @param selected true if selected, false otherwise */ void setSelected(boolean selected) { this.selected = selected; updateColors(); } public void setText(String string) { if(string != null && !string.equals(getText())) { text = string; redraw(); redraw(); } } /** * Sets the visibility of the cell's Toggle * @param visible */ public void setToggleVisible(boolean visible) { toggleVisible = visible; updateVisibility(); } /** * Sets the visibility of the cell's Toggle * <p>If ghost is true, then the toggle will occupy space even when not visible * - this is especially usefull for building trees</p> * @param visible * @param ghost */ public void setToggleVisible(boolean visible, boolean ghost) { toggleVisible = visible; ghostToggle = ghost; updateVisibility(); } /** * Sets the visibility of the cell * @param visible */ public void setVisible(boolean visible) { this.visible = visible; updateVisibility(); } /** * Called during setBounds. To be implemented in sub-classes * @param bounds */ protected void snap(Rectangle bounds) { // subclasses to implement } // public void setOpen(boolean open) { // if(((CTreeItem) item).getTreeCell() == this) { // if(this.open != open) { // this.open = open; // container.layout(open ? SWT.Expand : SWT.Collapse, item); // } // } else { // super.setOpen(open); // } // } /** * Updates this cell with its item's data object, as returned by * item.getData(). If getData() returns null, then this method simply returns false. * @return true if this cell (and thus maybe the container) needs its layout updated * @see #update(Object, String[]) */ public boolean update() { Object obj = null; if(!item.isDisposed()) { obj = item.getData(); } return (obj == null) ? false : update(obj, null); } /** * Updates the given properties of this CTableCell with the given element * <p>Functionality is only provided for a basic cell, subclasses should override</p> * @param element the data to use in updating the cell * @param properties the properties of the cell to be updated * @return true if this cell (and thus maybe its container) needs its layout updated, * actually calling layout on the cell and/or the CContainer is left up to the caller so that * performance improvements can be made - for instance, if several cells are updated, the CContainer * need only be updated once. */ public boolean update(Object element, String[] properties) { // TODO rework the JFace style 'update' functionality return false; } /** * Update the background and foreground colors for the cell and any contained * controls. This method should be called whenever something may change the color * of the cell, such as a change in selection state. */ protected void updateColors() { Color back; Color fore; if(selected) { back = item.ctree.getColors().getItemBackgroundSelected(); fore = item.ctree.getColors().getItemForegroundSelected(); } else if(isGridLine){ back = (storedBackground != null) ? storedBackground : item.ctree.getColors().getGrid(); fore = (storedForeground != null) ? storedForeground : item.ctree.getColors().getItemForegroundNormal(); } else { back = (storedBackground != null) ? storedBackground : item.ctree.getColors().getItemBackgroundNormal(); fore = (storedForeground != null) ? storedForeground : item.ctree.getColors().getItemForegroundNormal(); } activeBackground = back; activeForeground = fore; for(Iterator iter = getColorManagedControls().iterator(); iter.hasNext(); ) { Control c = (Control) iter.next(); c.setBackground(activeBackground); } } private void updateVisibility() { // TODO: redo updateVisibility // if(titleArea != null && !titleArea.isDisposed()) { // List controls = getControls(titleArea); // for(Iterator i = controls.iterator(); i.hasNext(); ) { // ((Control) i.next()).setVisible(titleVisible&&visible); // } // titleArea.setVisible(titleVisible&&visible); // } // if(childArea != null && !childArea.isDisposed()) { // List controls = getControls(childArea); // for(Iterator i = controls.iterator(); i.hasNext(); ) { // ((Control) i.next()).setVisible(childVisible&&open&&visible); // } // childArea.setVisible(childVisible&&visible); // } } }