blob: c9832cc8ef4b9debd8e305745670e63f2acb001d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2006 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.xml.ui.internal.tabletree;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ICellEditorListener;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.PlatformUI;
public class TreeExtension implements PaintListener {
protected Tree fTree;
protected EditManager editManager;
protected String[] fColumnProperties;
protected ICellModifier cellModifier;
protected int columnPosition = 300;
protected int columnHitWidth = 5;
protected Color tableLineColor;
protected int controlWidth;
protected DelayedDrawTimer delayedDrawTimer;
private boolean fisUnsupportedInput = false;
public TreeExtension(Tree tree) {
this.fTree = tree;
InternalMouseListener listener = new InternalMouseListener();
tree.addMouseMoveListener(listener);
tree.addMouseListener(listener);
tree.addPaintListener(this);
editManager = new EditManager(tree);
delayedDrawTimer = new DelayedDrawTimer(tree);
tableLineColor = tree.getDisplay().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
}
public void dispose() {
tableLineColor.dispose();
}
public void setCellModifier(ICellModifier modifier) {
cellModifier = modifier;
}
public void resetCachedData() {
// todo: sure seems we should reset something?
}
public ICellModifier getCellModifier() {
return cellModifier;
}
public List getItemList() {
List list = new Vector();
getItemListHelper(fTree.getItems(), list);
return list;
}
protected void getItemListHelper(TreeItem[] items, List list) {
for (int i = 0; i < items.length; i++) {
TreeItem item = items[i];
list.add(item);
getItemListHelper(item.getItems(), list);
}
}
protected TreeItem getTreeItemOnRow(int px, int py) {
TreeItem result = null;
List list = getItemList();
for (Iterator i = list.iterator(); i.hasNext();) {
TreeItem item = (TreeItem) i.next();
Rectangle r = item.getBounds();
if ((r != null) && (px >= r.x) && (py >= r.y) && (py <= r.y + r.height)) {
result = item;
}
}
return result;
}
protected class InternalMouseListener extends MouseAdapter implements MouseMoveListener {
protected int columnDragged = -1;
protected boolean isDown = false;
protected int prevX = -1;
protected Cursor cursor = null;
public void mouseMove(MouseEvent e) {
if ((e.x > columnPosition - columnHitWidth) && (e.x < columnPosition + columnHitWidth)) {
if (cursor == null) {
cursor = new Cursor(fTree.getDisplay(), SWT.CURSOR_SIZEWE);
fTree.setCursor(cursor);
}
}
else {
if (cursor != null) {
fTree.setCursor(null);
cursor.dispose();
cursor = null;
}
}
if (columnDragged != -1) {
// using the delay timer will make redraws less flickery
if (e.x > 20) {
columnPosition = e.x;
delayedDrawTimer.reset(20);
}
}
}
public void mouseDown(MouseEvent e) {
// here we handle the column resizing by detect if the user has
// click on a column separator
//
columnDragged = -1;
editManager.deactivateCellEditor();
if ((e.x > columnPosition - columnHitWidth) && (e.x < columnPosition + columnHitWidth)) {
columnDragged = 0;
}
// here we handle selecting tree items when any thing on the 'row'
// is clicked
//
TreeItem item = fTree.getItem(new Point(e.x, e.y));
if (item == null) {
item = getTreeItemOnRow(e.x, e.y);
if (item != null) {
TreeItem[] items = new TreeItem[1];
items[0] = item;
fTree.setSelection(items);
}
}
}
public void mouseUp(MouseEvent e) {
columnDragged = -1;
}
}
public String[] getColumnProperties() {
return fColumnProperties;
}
public void setColumnProperties(String[] columnProperties) {
this.fColumnProperties = columnProperties;
}
public void paintControl(PaintEvent event) {
GC gc = event.gc;
Rectangle treeBounds = fTree.getBounds();
controlWidth = treeBounds.width;
Color bg = fTree.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
Color bg2 = fTree.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
gc.setBackground(bg2);
// // This next part causes problems on LINUX, so let's not do it
// there
// if (B2BHacks.IS_UNIX == false) {
// TreeItem[] selectedItems = tree.getSelection();
// if (selectedItems.length > 0) {
// for (int i = 0; i < selectedItems.length; i++) {
// TreeItem item = selectedItems[i];
// Rectangle bounds = item.getBounds();
// if (bounds != null) {
// gc.fillRectangle(bounds.x + bounds.width, bounds.y, controlWidth,
// bounds.height);
// }
// }
// }
// }
//
if (!fisUnsupportedInput) {
TreeItem[] items = fTree.getItems();
if (items.length > 0) {
gc.setForeground(tableLineColor);
gc.setBackground(bg);
gc.fillRectangle(columnPosition, treeBounds.x, treeBounds.width, treeBounds.height);
Rectangle itemBounds = items[0].getBounds();
int height = computeTreeItemHeight();
if (itemBounds != null) {
/*
* Bounds will be for the first item, which will either be
* visible at the top of the Tree, or scrolled off with
* negative values
*/
int startY = itemBounds.y;
/* Only draw lines within the Tree boundaries */
for (int i = startY; i < treeBounds.height; i += height) {
if (i >= treeBounds.y) {
gc.drawLine(0, i, treeBounds.width, i);
}
}
}
gc.drawLine(columnPosition, 0, columnPosition, treeBounds.height);
paintItems(gc, items, treeBounds);
}
else {
addEmptyTreeMessage(gc);
}
}
else {
addUnableToPopulateTreeMessage(gc);
}
}
protected int computeTreeItemHeight() {
int result = -1;
/*
* On GTK tree.getItemHeight() seems to lie to us. It reports that the
* tree item occupies a few pixles less vertical space than it should
* (possibly because of the image height vs. the text height?). This
* foils our code that draws the 'row' lines since we assume that
* lines should be drawn at 'itemHeight' increments. Don't trust
* getItemHeight() to compute the increment... instead compute the
* value based on distance between two TreeItems, and then use the
* larger value.
*
* This strategy only works on trees where the items are of even
* height, however bug
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=117201 indicates that
* this is no longer promised, at least on win32 and likely on other
* platforms soon.
*/
if (fTree.getItemCount() > 0) {
TreeItem[] items = fTree.getItems();
Rectangle itemBounds = items[0].getBounds();
if (items[0].getExpanded()) {
TreeItem[] children = items[0].getItems();
if (children.length > 0) {
result = children[0].getBounds().y - itemBounds.y;
}
}
else if (items.length > 1) {
result = items[1].getBounds().y - itemBounds.y;
}
}
result = Math.max(fTree.getItemHeight(), result);
return result;
}
protected void addEmptyTreeMessage(GC gc) {
// nothing to add here
}
private void addUnableToPopulateTreeMessage(GC gc) {
// here we print a message when the document cannot be displayed just
// to give the
// user a visual cue
// so that they know how to proceed to edit the blank view
gc.setForeground(fTree.getDisplay().getSystemColor(SWT.COLOR_BLACK));
gc.setBackground(fTree.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
gc.drawString(XMLEditorMessages.TreeExtension_0, 10, 10);
}
void setIsUnsupportedInput(boolean isUnsupported) {
fisUnsupportedInput = isUnsupported;
}
public void paintItems(GC gc, TreeItem[] items, Rectangle treeBounds) {
if (items != null) {
for (int i = 0; i < items.length; i++) {
TreeItem item = items[i];
if (item != null) {
Rectangle bounds = item.getBounds();
if (bounds != null) {
if (treeBounds.intersects(bounds)) {
paintItem(gc, item, bounds);
}
}
// defect 241039
//
if (item.getExpanded()) {
paintItems(gc, item.getItems(), treeBounds);
}
}
}
}
}
protected void paintItem(GC gc, TreeItem item, Rectangle bounds) {
// nothing to paint
}
public interface ICellEditorProvider {
CellEditor getCellEditor(Object o, int col);
}
/**
* This class is used to improve drawing during a column resize.
*/
public class DelayedDrawTimer implements Runnable {
protected Control control;
public DelayedDrawTimer(Control control1) {
this.control = control1;
}
public void reset(int milliseconds) {
getDisplay().timerExec(milliseconds, this);
}
public void run() {
control.redraw();
}
}
Display getDisplay() {
return PlatformUI.getWorkbench().getDisplay();
}
/**
* EditManager
*/
public class EditManager {
protected Tree fTree1;
protected Control cellEditorHolder;
protected CellEditorState cellEditorState;
public EditManager(Tree tree) {
this.fTree1 = tree;
this.cellEditorHolder = new Composite(tree, SWT.NONE);
final Tree theTree = tree;
MouseAdapter theMouseAdapter = new MouseAdapter() {
public void mouseDown(MouseEvent e) {
deactivateCellEditor();
if (e.x > columnPosition + columnHitWidth) {
TreeItem[] items = theTree.getSelection();
// No edit if more than one row is selected.
if (items.length == 1) {
Rectangle bounds = items[0].getBounds();
if ((bounds != null) && (e.y >= bounds.y) && (e.y <= bounds.y + bounds.height)) {
int columnToEdit = 1;
activateCellEditor(items[0], columnToEdit);
}
}
}
}
};
SelectionListener selectionListener = new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent e) {
applyCellEditorValue();
}
public void widgetSelected(SelectionEvent e) {
applyCellEditorValue();
}
};
KeyListener keyListener = new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.character == SWT.CR) {
deactivateCellEditor();
TreeItem[] items = theTree.getSelection();
if (items.length == 1) {
activateCellEditor(items[0], 1);
}
}
}
};
tree.addMouseListener(theMouseAdapter);
tree.addKeyListener(keyListener);
ScrollBar hBar = tree.getHorizontalBar();
if (hBar != null) {
hBar.addSelectionListener(selectionListener);
}
ScrollBar vBar = tree.getVerticalBar();
if (vBar != null) {
vBar.addSelectionListener(selectionListener);
}
}
public boolean isCellEditorActive() {
return cellEditorState != null;
}
public void applyCellEditorValue() {
if ((cellEditorState != null) && (cellModifier != null)) {
TreeItem treeItem = cellEditorState.fTreeItem;
// The area below the cell editor needs to be explicity
// repainted on Linux
//
// Rectangle r = B2BHacks.IS_UNIX ? treeItem.getBounds() :
// null;
Object value = cellEditorState.fCellEditor.getValue();
String property = cellEditorState.fProperty;
deactivateCellEditor();
cellModifier.modify(treeItem, property, value);
// if (r != null) {
// tree.redraw(r.x, r.y, tree.getBounds().width, r.height,
// false);
// }
}
}
public void deactivateCellEditor() {
// Clean up any previous editor control
if (cellEditorState != null) {
cellEditorState.deactivate();
cellEditorState = null;
}
}
public void activateCellEditor(TreeItem treeItem, int column) {
if (cellModifier instanceof ICellEditorProvider) {
ICellEditorProvider cellEditorProvider = (ICellEditorProvider) cellModifier;
Object data = treeItem.getData();
if (fColumnProperties.length > column) {
String property = fColumnProperties[column];
if (cellModifier.canModify(data, property)) {
CellEditor newCellEditor = cellEditorProvider.getCellEditor(data, column);
if (newCellEditor != null) {
// The control that will be the editor must be a
// child of the columnPosition
Control control = newCellEditor.getControl();
if (control != null) {
cellEditorState = new CellEditorState(newCellEditor, control, treeItem, column, property);
cellEditorState.activate();
}
}
}
}
}
}
/**
* this class holds the state that is need on a per cell editor
* invocation basis
*/
public class CellEditorState implements ICellEditorListener, FocusListener {
public CellEditor fCellEditor;
public Control fControl;
public TreeItem fTreeItem;
public int fColumnNumber;
public String fProperty;
public CellEditorState(CellEditor cellEditor, Control control, TreeItem treeItem, int columnNumber, String property) {
this.fCellEditor = cellEditor;
this.fControl = control;
this.fTreeItem = treeItem;
this.fColumnNumber = columnNumber;
this.fProperty = property;
}
public void activate() {
Object element = fTreeItem.getData();
String value = cellModifier.getValue(element, fProperty).toString();
if (fControl instanceof Text) {
Text text = (Text) fControl;
int requiredSize = value.length() + 100;
if (text.getTextLimit() < requiredSize) {
text.setTextLimit(requiredSize);
}
}
Rectangle r = fTreeItem.getBounds();
if (r != null) {
fControl.setBounds(columnPosition + 5, r.y + 1, fTree1.getClientArea().width - (columnPosition + 5), r.height - 1);
fControl.setVisible(true);
fCellEditor.setValue(value);
fCellEditor.addListener(this);
fCellEditor.setFocus();
fControl.addFocusListener(this);
}
}
public void deactivate() {
fCellEditor.removeListener(this);
fControl.removeFocusListener(this);
fCellEditor.deactivate();
fTree1.forceFocus();
}
// ICellEditorListener methods
//
public void applyEditorValue() {
applyCellEditorValue();
}
public void cancelEditor() {
deactivateCellEditor();
}
public void editorValueChanged(boolean oldValidState, boolean newValidState) {
// nothing, for now
}
// FocusListener methods
//
public void focusGained(FocusEvent e) {
// do nothing
}
public void focusLost(FocusEvent e) {
applyCellEditorValue();
}
}
}
}