package de.ovgu.cide.editor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.swt.graphics.Point;
import cide.gast.IASTNode;
import cide.gast.ISourceFile;
import cide.gparser.ParseException;
import de.ovgu.cide.features.IFeature;
import de.ovgu.cide.features.source.ColoredSourceFile;
import de.ovgu.cide.features.source.DirectoryColorManager;
import de.ovgu.cide.features.source.SourceFileColorManager;
import de.ovgu.cide.utils.ColorHelper;
/**
* this class shows a tooltip for a code fragment (either the node under the
* mouse pointer or (if existent) what's common for all nodes in the selected
* region)
*
* it is registered by the ColoredSourceViewerConfiguration for all colored
* editors
*
* complexity is due to the fact that multiple nodes can be selected and only
* the common colors are shown. similar mechanism as in SelectionActionsContext
*
*
* @author ckaestne
*
*/
public class ColoredTextHover implements ITextHover {
protected static final String NOT_COLORED = "Selected code fragment cannot be colored";
protected String NL = "\n";
protected ColoredSourceFile sourceFile;
// private final IProject project;
private SourceFileColorManager colorManager;
public ColoredTextHover(ColoredSourceFile sourceFile) {
setColoredSourceFile(sourceFile);
}
protected void setColoredSourceFile(ColoredSourceFile sourceFile) {
this.sourceFile = sourceFile;
if (sourceFile != null)
this.colorManager = sourceFile.getColorManager();
else
this.colorManager = null;
}
public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
if (hoverRegion == null)
return null;
// find colors
List<IASTNode> nodes = findSelectedNodes(hoverRegion);
if ((nodes == null || nodes.isEmpty()) && hoverRegion.getLength() == 0)
return null;
if (nodes == null || nodes.isEmpty())
return NOT_COLORED;
String tooltip = "";
tooltip += tooltipNodes(nodes);
String ttc = tooltipColors(nodes);
if (ttc == null)
return null;
tooltip += ttc;
return tooltip.trim();
}
private String tooltipNodes(List<IASTNode> nodes) {
assert nodes.size() > 0;
String result = "";
for (int nodeidx = 0; nodeidx < nodes.size() && nodeidx < 5; nodeidx++) {
String aststr = getASTStringOutput(nodes.get(nodeidx));
result += "\"" + aststr + "\""+NL;
}
if (nodes.size() > 5)
result += "..."+NL;
return result + NL;
}
private String getASTStringOutput(IASTNode node) {
String aststr = node.render().trim();
aststr = aststr.replace('\n', ' ').replace('\r', ' ')
.replace('\t', ' ');
if (aststr.length() > 60) {
aststr = aststr.substring(0, 50) + " ... "
+ aststr.substring(aststr.length() - 5);
}
return aststr;
}
private String tooltipColors(Collection<IASTNode> nodes) {
assert nodes.size() > 0;
String tooltip = "";
// all colors
Set<IFeature> allColors = getAllColors(nodes);
tooltip += printColors(
(nodes.size() > 1 ? "Common " : "") + "Features", allColors);
// details (direct colors, inherited colors, file colors)
tooltip += NL;
Set<IFeature> directColors = getDirectColors(nodes);
if (directColors.size() > 0)
tooltip += printColorsShort("Direct Colors", directColors);
else
tooltip += "No direct colors."+NL;
Set<IFeature> inheritedColors = getInheritedColors(nodes);
if (inheritedColors.size() > 0)
tooltip += printColorsShort("Inherited Colors", inheritedColors);
Set<IFeature> fileColors = getFileColors();
if (fileColors.size() > 0)
tooltip += printColorsShort("File Colors", fileColors);
// for no colors whatsoever return no tooltip
if (directColors.size() == 0 && inheritedColors.size() == 0
&& fileColors.size() == 0)
return null;
return tooltip;
}
private Set<IFeature> getFileColors() {
DirectoryColorManager dirColorManager = DirectoryColorManager
.getColoredDirectoryManagerS(sourceFile.getResource()
.getParent(), sourceFile.getFeatureModel());
return dirColorManager.getColors(sourceFile.getResource());
}
private Set<IFeature> getInheritedColors(Collection<IASTNode> nodes) {
// all inherited colors, but not the file colors
assert nodes.size() > 0;
Set<IFeature> result = new HashSet<IFeature>();
Iterator<IASTNode> i = nodes.iterator();
result.addAll(colorManager.getInheritedColors(i.next()));
while (i.hasNext()) {
join(result, colorManager.getInheritedColors(i.next()));
}
result.removeAll(getFileColors());
return result;
}
private Set<IFeature> getDirectColors(Collection<IASTNode> nodes) {
assert nodes.size() > 0;
Set<IFeature> result = new HashSet<IFeature>();
Iterator<IASTNode> i = nodes.iterator();
result.addAll(colorManager.getOwnColors(i.next()));
while (i.hasNext()) {
join(result, colorManager.getOwnColors(i.next()));
}
return result;
}
/**
* removes all entries from target that are not contained in newEntries
*
* @param target
* @param newEntries
*/
private void join(Set<IFeature> target, Set<IFeature> newEntries) {
for (Iterator<IFeature> i = target.iterator(); i.hasNext();) {
if (!newEntries.contains(i.next()))
i.remove();
}
}
private Set<IFeature> getAllColors(Collection<IASTNode> nodes) {
assert nodes.size() > 0;
Set<IFeature> result = new HashSet<IFeature>();
Iterator<IASTNode> i = nodes.iterator();
result.addAll(colorManager.getColors(i.next()));
while (i.hasNext()) {
join(result, colorManager.getColors(i.next()));
}
return result;
}
private List<IASTNode> findSelectedNodes(IRegion hoverRegion) {
ISourceFile ast;
try {
ast = sourceFile.getAST();
} catch (CoreException e) {
e.printStackTrace();
return null;
} catch (ParseException e) {
e.printStackTrace();
return null;
}
ArrayList<IASTNode> result = new ArrayList<IASTNode>();
if (hoverRegion.getLength() == 0) {
SingleNodeFinder snf = new SingleNodeFinder(hoverRegion.getOffset());
ast.accept(snf);
IASTNode node = snf.result;
// Ist kein Text markiert, so suchen wir nach dem n�chsten
// optionalen Knoten. Sonst w�rde man z.B.
// bei einem gef�rbten "Object o1;" die Angabe "no direct colors"
// bekommen, da der Cursor entweder
// �ber dem Knoten "Object" oder "o1" steht.
// Nachteil: Ist z.B. nur der R�ckgabetyp einer Methode gef�rbt, so
// bekommt man die Angabe "no direct colors".
// Den Aufwand, um dies zu vermeiden, ist es nicht Wert :-)
while (node != null && !node.isOptional())
node = node.getParent();
if (node != null)
result.add(node);
} else
ast.accept(new SelectionFinder(result, hoverRegion, false)); // Uns
// interessieren
// nun
// auch
// nicht-optionale
// Knoten
// (wg.
// alternativen
// Features)
return result;
}
private String printColors(String title, Collection<IFeature> colors) {
String result = title + ":"+NL;
List<IFeature> sortedColors = ColorHelper.sortFeatures(colors);
for (IFeature color : sortedColors) {
result += " - " + color.getName() + NL;
}
return result;
}
private String printColorsShort(String title, Collection<IFeature> colors) {
String result = title + ": ";
List<IFeature> sortedColors = ColorHelper.sortFeatures(colors);
boolean first = true;
for (IFeature color : sortedColors) {
if (first)
first = false;
else
result += ", ";
result += color.getName();
}
return result + NL;
}
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
Point selection = textViewer.getSelectedRange();
if (selection.x <= offset && offset < selection.x + selection.y)
return new Region(selection.x, selection.y);
return new Region(offset, 0);
}
}