/******************************************************************************* * 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(); } } } }