blob: e940633b606081846da369d488ae5016b561cab2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 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.css.ui.internal.projection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode;
import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter;
import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.xml.ui.internal.Logger;
/**
* Updates projection annotation model with projection annotations for this
* adapter node's children
*/
public class ProjectionModelNodeAdapterCSS implements INodeAdapter {
private class TagProjectionAnnotation extends ProjectionAnnotation {
private boolean fIsVisible = false; /* workaround for BUG85874 */
private ICSSNode fNode;
public TagProjectionAnnotation(ICSSNode node, boolean isCollapsed) {
super(isCollapsed);
fNode = node;
}
public ICSSNode getNode() {
return fNode;
}
public void setNode(ICSSNode node) {
fNode = node;
}
/**
* Does not paint hidden annotations. Annotations are hidden when they
* only span one line.
*
* @see ProjectionAnnotation#paint(org.eclipse.swt.graphics.GC,
* org.eclipse.swt.widgets.Canvas,
* org.eclipse.swt.graphics.Rectangle)
*/
public void paint(GC gc, Canvas canvas, Rectangle rectangle) {
/* workaround for BUG85874 */
/*
* only need to check annotations that are expanded because hidden
* annotations should never have been given the chance to
* collapse.
*/
if (!isCollapsed()) {
// working with rectangle, so need the styledtext line height
int lineHeight = fAdapterFactory.getProjectionViewer().getTextWidget().getLineHeight();
// do not draw annotations that only span one line and mark
// them as not visible
if ((rectangle.height / lineHeight) <= 1) {
fIsVisible = false;
return;
}
}
fIsVisible = true;
super.paint(gc, canvas, rectangle);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.source.projection.ProjectionAnnotation#markCollapsed()
*/
public void markCollapsed() {
/* workaround for BUG85874 */
// do not mark collapsed if annotation is not visible
if (fIsVisible)
super.markCollapsed();
}
}
private final static boolean debugProjectionPerf = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.css.ui/projectionperf")); //$NON-NLS-1$ //$NON-NLS-2$\
ProjectionModelNodeAdapterFactoryCSS fAdapterFactory;
private Map fTagAnnotations = new HashMap();
public ProjectionModelNodeAdapterCSS(ProjectionModelNodeAdapterFactoryCSS factory) {
fAdapterFactory = factory;
}
/**
* Create a projection position from the given node. Able to get
* projection position if node isNodeProjectable.
*
* @param node
* @return null if no projection position possible, a Position otherwise
*/
private Position createProjectionPosition(ICSSNode node) {
Position pos = null;
if (isNodeProjectable(node) && node instanceof IndexedRegion) {
IDocument document = fAdapterFactory.getProjectionViewer().getDocument();
if (document != null) {
IndexedRegion inode = (IndexedRegion) node;
int start = inode.getStartOffset();
int end = inode.getEndOffset();
if (start >= 0 && start < end) {
try {
// // region-based
// // extra line when collapsed, but no region increase when add newline
// pos = new Position(start, end - start);
// // line-based
// // extra line when collapsed, but no region increase when add newline
// IRegion startLineRegion = document.getLineInformationOfOffset(start);
// IRegion endLineRegion = document.getLineInformationOfOffset(end);
// int startOffset = startLineRegion.getOffset();
// int endOffset = endLineRegion.getOffset() + endLineRegion.getLength();
// if (endOffset > startOffset) {
// pos = new Position(startOffset, endOffset - startOffset);
// }
// line-based
// no extra line when collapsed, but region increase when add newline
int startLine = document.getLineOfOffset(start);
int endLine = document.getLineOfOffset(end);
if (endLine + 1 < document.getNumberOfLines()) {
int offset = document.getLineOffset(startLine);
int endOffset = document.getLineOffset(endLine + 1);
pos = new Position(offset, endOffset - offset);
}
} catch (BadLocationException x) {
Logger.log(Logger.WARNING_DEBUG, null, x);
}
}
}
}
return pos;
}
/**
* Find TagProjectionAnnotation for node in the current list of projection
* annotations for this adapter
*
* @param node
* @return TagProjectionAnnotation
*/
private TagProjectionAnnotation getExistingAnnotation(ICSSNode node) {
TagProjectionAnnotation anno = null;
if ((node != null) && (!fTagAnnotations.isEmpty())) {
Iterator it = fTagAnnotations.keySet().iterator();
while (it.hasNext() && anno == null) {
TagProjectionAnnotation a = (TagProjectionAnnotation) it.next();
ICSSNode n = a.getNode();
if (node.equals(n)) {
anno = a;
}
}
}
return anno;
}
public boolean isAdapterForType(Object type) {
return type == ProjectionModelNodeAdapterCSS.class;
}
/**
* Returns true if node is a node type able to fold
*
* @param node
* @return boolean true if node is projectable, false otherwise
*/
private boolean isNodeProjectable(ICSSNode node) {
if (node != null) {
short type = node.getNodeType();
if (type == ICSSNode.STYLERULE_NODE || type == ICSSNode.PAGERULE_NODE || type == ICSSNode.MEDIARULE_NODE || type == ICSSNode.IMPORTRULE_NODE || type == ICSSNode.FONTFACERULE_NODE || type == ICSSNode.CHARSETRULE_NODE)
return true;
}
return false;
}
public void notifyChanged(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) {
// check if folding is even enabled, if not, just ignore notifyChanged
// events
if (fAdapterFactory.getProjectionViewer() == null) {
return;
}
// workaround for Bug85342 - STRUCTRE_CHANGED is never fired so need
// to listen for every ADD, REMOVE
// if ((eventType == INodeNotifier.STRUCTURE_CHANGED) && (notifier
// instanceof ICSSNode)) {
if ((eventType == INodeNotifier.ADD || eventType == INodeNotifier.REMOVE) && (notifier instanceof ICSSNode)) {
updateAdapter((ICSSNode) notifier);
}
}
/**
* Update the projection annotation of all the nodes that are children of
* node
*
* @param node
*/
void updateAdapter(ICSSNode node) {
long start = System.currentTimeMillis();
Map additions = new HashMap();
Map projectionAnnotations = new HashMap();
// go through immediate child nodes and figure out projection
// model annotations
if (node != null) {
ICSSNode childNode = node.getFirstChild();
while (childNode != null) {
Position newPos = createProjectionPosition(childNode);
if (newPos != null) {
TagProjectionAnnotation newAnnotation = new TagProjectionAnnotation(childNode, false);
TagProjectionAnnotation existing = getExistingAnnotation(childNode);
if (existing == null) {
// add to map containing all annotations for this
// adapter
projectionAnnotations.put(newAnnotation, newPos);
// add to map containing annotations to add
additions.put(newAnnotation, newPos);
} else {
// add to map containing all annotations for this
// adapter
projectionAnnotations.put(existing, newPos);
// remove from map containing annotations to delete
fTagAnnotations.remove(existing);
}
}
childNode = childNode.getNextSibling();
}
// in the end, want to delete anything leftover in old list, add
// everything in additions, and update everything in
// projectionAnnotations
ProjectionAnnotation[] oldList = null;
if (!fTagAnnotations.isEmpty()) {
oldList = (ProjectionAnnotation[]) fTagAnnotations.keySet().toArray(new ProjectionAnnotation[0]);
}
ProjectionAnnotation[] modifyList = null;
if (!projectionAnnotations.isEmpty()) {
modifyList = (ProjectionAnnotation[]) projectionAnnotations.keySet().toArray(new ProjectionAnnotation[0]);
}
ProjectionViewer viewer = fAdapterFactory.getProjectionViewer();
ProjectionAnnotationModel annotationModel = viewer.getProjectionAnnotationModel();
annotationModel.modifyAnnotations(oldList, additions, modifyList);
}
// save new list of annotations
fTagAnnotations = additions;
long end = System.currentTimeMillis();
if (debugProjectionPerf) {
String nodeName = node != null ? node.toString() : "null"; //$NON-NLS-1$
System.out.println("ProjectionModelNodeAdapterCSS.updateAdapter (" + nodeName + "):" + (end - start)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}