/* License: GPL. Copyright 2007 by Immanuel Scholz and others */
package org.openstreetmap.josm.data.osm.visitor.paint;
/* To enable debugging or profiling remove the double / signs */
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.BBox;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmUtils;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
import org.openstreetmap.josm.gui.DefaultNameFormatter;
import org.openstreetmap.josm.gui.NavigatableComponent;
import org.openstreetmap.josm.gui.mappaint.AreaElemStyle;
import org.openstreetmap.josm.gui.mappaint.ElemStyle;
import org.openstreetmap.josm.gui.mappaint.ElemStyles;
import org.openstreetmap.josm.gui.mappaint.IconElemStyle;
import org.openstreetmap.josm.gui.mappaint.LineElemStyle;
import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
import org.openstreetmap.josm.gui.mappaint.SimpleNodeElemStyle;
public class MapPaintVisitor implements PaintVisitor {
private Graphics2D g;
private NavigatableComponent nc;
private boolean zoomLevelDisplay;
private boolean drawMultipolygon;
private boolean drawRestriction;
private boolean leftHandTraffic;
private ElemStyles.StyleSet styles;
private double circum;
private double dist;
private boolean useStyleCache;
private static int paintid = 0;
private EastNorth minEN;
private EastNorth maxEN;
private MapPainter painter;
private MapPaintSettings paintSettings;
private boolean inactive;
protected boolean isZoomOk(ElemStyle e) {
if (!zoomLevelDisplay) /* show everything if the user wishes so */
return true;
if(e == null) /* the default for things that don't have a rule (show, if scale is smaller than 1500m) */
return (circum < 1500);
return !(circum >= e.maxScale || circum < e.minScale);
}
public ElemStyle getPrimitiveStyle(OsmPrimitive osm) {
if(!useStyleCache)
return ((styles != null) ? styles.get(osm) : null);
if(osm.mappaintStyle == null && styles != null) {
osm.mappaintStyle = styles.get(osm);
if(osm instanceof Way) {
((Way)osm).isMappaintArea = styles.isArea(osm);
}
}
if (osm.mappaintStyle == null && osm instanceof Node) {
osm.mappaintStyle = SimpleNodeElemStyle.INSTANCE;
}
if (osm.mappaintStyle == null && osm instanceof Way) {
osm.mappaintStyle = LineElemStyle.UNTAGGED_WAY;
}
return osm.mappaintStyle;
}
public IconElemStyle getPrimitiveNodeStyle(OsmPrimitive osm) {
if(!useStyleCache)
return (styles != null) ? styles.getIcon(osm) : null;
if(osm.mappaintStyle == null && styles != null) {
osm.mappaintStyle = styles.getIcon(osm);
}
return (IconElemStyle)osm.mappaintStyle;
}
public boolean isPrimitiveArea(Way osm) {
if(!useStyleCache)
return styles.isArea(osm);
if(osm.mappaintStyle == null && styles != null) {
osm.mappaintStyle = styles.get(osm);
osm.isMappaintArea = styles.isArea(osm);
}
return osm.isMappaintArea;
}
public void drawNode(Node n) {
/* check, if the node is visible at all */
if((n.getEastNorth().east() > maxEN.east() ) ||
(n.getEastNorth().north() > maxEN.north()) ||
(n.getEastNorth().east() < minEN.east() ) ||
(n.getEastNorth().north() < minEN.north()))
return;
ElemStyle nodeStyle = getPrimitiveStyle(n);
if (isZoomOk(nodeStyle)) {
nodeStyle.paintPrimitive(n, paintSettings, painter, n.isSelected());
}
}
public void drawWay(Way w, int fillAreas) {
if(w.getNodesCount() < 2)
return;
if (w.hasIncompleteNodes())
return;
/* check, if the way is visible at all */
double minx = 10000;
double maxx = -10000;
double miny = 10000;
double maxy = -10000;
for (Node n : w.getNodes())
{
if(n.getEastNorth().east() > maxx) {
maxx = n.getEastNorth().east();
}
if(n.getEastNorth().north() > maxy) {
maxy = n.getEastNorth().north();
}
if(n.getEastNorth().east() < minx) {
minx = n.getEastNorth().east();
}
if(n.getEastNorth().north() < miny) {
miny = n.getEastNorth().north();
}
}
if ((minx > maxEN.east()) ||
(miny > maxEN.north()) ||
(maxx < minEN.east()) ||
(maxy < minEN.north()))
return;
ElemStyle wayStyle = getPrimitiveStyle(w);
if(!isZoomOk(wayStyle))
return;
if (wayStyle == null) {
wayStyle = LineElemStyle.UNTAGGED_WAY;
}
if(wayStyle instanceof LineElemStyle) {
wayStyle.paintPrimitive(w, paintSettings, painter, data.isSelected(w));
} else if (wayStyle instanceof AreaElemStyle) {
AreaElemStyle areaStyle = (AreaElemStyle) wayStyle;
/* way with area style */
if (fillAreas > dist)
{
painter.drawArea(getPolygon(w), (data.isSelected(w) ? paintSettings.getSelectedColor() : areaStyle.color), painter.getWayName(w));
if(!w.isClosed()) {
putError(w, tr("Area style way is not closed."), true);
}
}
areaStyle.getLineStyle().paintPrimitive(w, paintSettings, painter, data.isSelected(w));
}
}
public void drawSelectedMember(OsmPrimitive osm, ElemStyle style, boolean area,
boolean areaselected)
{
if(osm instanceof Way)
{
if(style instanceof AreaElemStyle) {
Way way = (Way)osm;
AreaElemStyle areaStyle = (AreaElemStyle)style;
areaStyle.getLineStyle().paintPrimitive(way, paintSettings, painter, true);
if(area) {
painter.drawArea(getPolygon(way), (areaselected ? paintSettings.getSelectedColor() : areaStyle.color), painter.getWayName(way));
}
} else {
style.paintPrimitive(osm, paintSettings, painter, true);
}
}
else if(osm instanceof Node)
{
if(isZoomOk(style)) {
style.paintPrimitive(osm, paintSettings, painter, true);
}
}
osm.mappaintDrawnCode = paintid;
}
public void paintUnselectedRelation(Relation r) {
if (drawMultipolygon && "multipolygon".equals(r.get("type")))
{
if(drawMultipolygon(r))
return;
}
else if (drawRestriction && "restriction".equals(r.get("type")))
{
drawRestriction(r);
}
if(data.isSelected(r)) /* draw ways*/
{
for (RelationMember m : r.getMembers())
{
if (m.isWay() && m.getMember().isDrawable())
{
drawSelectedMember(m.getMember(), styles != null ? getPrimitiveStyle(m.getMember())
: null, true, true);
}
}
}
}
public void drawRestriction(Relation r) {
Way fromWay = null;
Way toWay = null;
OsmPrimitive via = null;
/* find the "from", "via" and "to" elements */
for (RelationMember m : r.getMembers())
{
if(m.getMember().isIncomplete())
return;
else
{
if(m.isWay())
{
Way w = m.getWay();
if(w.getNodesCount() < 2) {
continue;
}
if("from".equals(m.getRole())) {
if(fromWay != null) {
putError(r, tr("More than one \"from\" way found."), true);
} else {
fromWay = w;
}
} else if("to".equals(m.getRole())) {
if(toWay != null) {
putError(r, tr("More than one \"to\" way found."), true);
} else {
toWay = w;
}
} else if("via".equals(m.getRole())) {
if(via != null) {
putError(r, tr("More than one \"via\" found."), true);
} else {
via = w;
}
} else {
putError(r, tr("Unknown role ''{0}''.", m.getRole()), true);
}
}
else if(m.isNode())
{
Node n = m.getNode();
if("via".equals(m.getRole()))
{
if(via != null) {
putError(r, tr("More than one \"via\" found."), true);
} else {
via = n;
}
} else {
putError(r, tr("Unknown role ''{0}''.", m.getRole()), true);
}
} else {
putError(r, tr("Unknown member type for ''{0}''.", m.getMember().getDisplayName(DefaultNameFormatter.getInstance())), true);
}
}
}
if (fromWay == null) {
putError(r, tr("No \"from\" way found."), true);
return;
}
if (toWay == null) {
putError(r, tr("No \"to\" way found."), true);
return;
}
if (via == null) {
putError(r, tr("No \"via\" node or way found."), true);
return;
}
Node viaNode;
if(via instanceof Node)
{
viaNode = (Node) via;
if(!fromWay.isFirstLastNode(viaNode)) {
putError(r, tr("The \"from\" way does not start or end at a \"via\" node."), true);
return;
}
if(!toWay.isFirstLastNode(viaNode)) {
putError(r, tr("The \"to\" way does not start or end at a \"via\" node."), true);
}
}
else
{
Way viaWay = (Way) via;
Node firstNode = viaWay.firstNode();
Node lastNode = viaWay.lastNode();
Boolean onewayvia = false;
String onewayviastr = viaWay.get("oneway");
if(onewayviastr != null)
{
if("-1".equals(onewayviastr)) {
onewayvia = true;
Node tmp = firstNode;
firstNode = lastNode;
lastNode = tmp;
} else {
onewayvia = OsmUtils.getOsmBoolean(onewayviastr);
if (onewayvia == null) {
onewayvia = false;
}
}
}
if(fromWay.isFirstLastNode(firstNode)) {
viaNode = firstNode;
} else if (!onewayvia && fromWay.isFirstLastNode(lastNode)) {
viaNode = lastNode;
} else {
putError(r, tr("The \"from\" way does not start or end at the \"via\" way."), true);
return;
}
if(!toWay.isFirstLastNode(viaNode == firstNode ? lastNode : firstNode)) {
putError(r, tr("The \"to\" way does not start or end at the \"via\" way."), true);
}
}
/* find the "direct" nodes before the via node */
Node fromNode = null;
if(fromWay.firstNode() == via) {
fromNode = fromWay.getNode(1);
} else {
fromNode = fromWay.getNode(fromWay.getNodesCount()-2);
}
Point pFrom = nc.getPoint(fromNode);
Point pVia = nc.getPoint(viaNode);
/* starting from via, go back the "from" way a few pixels
(calculate the vector vx/vy with the specified length and the direction
away from the "via" node along the first segment of the "from" way)
*/
double distanceFromVia=14;
double dx = (pFrom.x >= pVia.x) ? (pFrom.x - pVia.x) : (pVia.x - pFrom.x);
double dy = (pFrom.y >= pVia.y) ? (pFrom.y - pVia.y) : (pVia.y - pFrom.y);
double fromAngle;
if(dx == 0.0) {
fromAngle = Math.PI/2;
} else {
fromAngle = Math.atan(dy / dx);
}
double fromAngleDeg = Math.toDegrees(fromAngle);
double vx = distanceFromVia * Math.cos(fromAngle);
double vy = distanceFromVia * Math.sin(fromAngle);
if(pFrom.x < pVia.x) {
vx = -vx;
}
if(pFrom.y < pVia.y) {
vy = -vy;
}
/* go a few pixels away from the way (in a right angle)
(calculate the vx2/vy2 vector with the specified length and the direction
90degrees away from the first segment of the "from" way)
*/
double distanceFromWay=10;
double vx2 = 0;
double vy2 = 0;
double iconAngle = 0;
if(pFrom.x >= pVia.x && pFrom.y >= pVia.y) {
if(!leftHandTraffic) {
vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90));
vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90));
} else {
vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90));
vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90));
}
iconAngle = 270+fromAngleDeg;
}
if(pFrom.x < pVia.x && pFrom.y >= pVia.y) {
if(!leftHandTraffic) {
vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg));
vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg));
} else {
vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180));
vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180));
}
iconAngle = 90-fromAngleDeg;
}
if(pFrom.x < pVia.x && pFrom.y < pVia.y) {
if(!leftHandTraffic) {
vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90));
vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90));
} else {
vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90));
vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90));
}
iconAngle = 90+fromAngleDeg;
}
if(pFrom.x >= pVia.x && pFrom.y < pVia.y) {
if(!leftHandTraffic) {
vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180));
vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180));
} else {
vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg));
vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg));
}
iconAngle = 270-fromAngleDeg;
}
IconElemStyle nodeStyle = getPrimitiveNodeStyle(r);
if (nodeStyle == null) {
putError(r, tr("Style for restriction {0} not found.", r.get("restriction")), true);
return;
}
painter.drawRestriction(inactive || r.isDisabled() ? nodeStyle.getDisabledIcon() : nodeStyle.icon,
pVia, vx, vx2, vy, vy2, iconAngle, data.isSelected(r));
}
public boolean drawMultipolygon(Relation r) {
boolean drawn = false;
Multipolygon multipolygon = new Multipolygon(nc);
multipolygon.load(r);
ElemStyle wayStyle = getPrimitiveStyle(r);
// If area style was not found for relation then use style of ways
if(styles != null && !(wayStyle instanceof AreaElemStyle)) {
for (Way w : multipolygon.getOuterWays()) {
wayStyle = styles.getArea(w);
if(wayStyle != null) {
break;
}
}
r.mappaintStyle = wayStyle;
}
if (wayStyle instanceof AreaElemStyle) {
boolean zoomok = isZoomOk(wayStyle);
boolean visible = false;
drawn = true;
if(zoomok && !multipolygon.getOuterWays().isEmpty()) {
AreaElemStyle areaStyle = (AreaElemStyle)wayStyle;
for (PolyData pd : multipolygon.getCombinedPolygons()) {
Polygon p = pd.get();
if(!isPolygonVisible(p)) {
continue;
}
boolean selected = pd.selected || data.isSelected(r);
painter.drawArea(p, selected ? paintSettings.getSelectedColor() : areaStyle.color, null);
visible = true;
}
}
if(!visible)
return drawn;
for (Way wInner : multipolygon.getInnerWays())
{
ElemStyle innerStyle = getPrimitiveStyle(wInner);
if(innerStyle == null)
{
if (data.isSelected(wInner)) {
continue;
}
if(zoomok && (wInner.mappaintDrawnCode != paintid || multipolygon.getOuterWays().isEmpty())) {
((AreaElemStyle)wayStyle).getLineStyle().paintPrimitive(wInner, paintSettings, painter, (data.isSelected(wInner)
|| data.isSelected(r)));
}
wInner.mappaintDrawnCode = paintid;
}
else
{
if(data.isSelected(r))
{
drawSelectedMember(wInner, innerStyle,
!wayStyle.equals(innerStyle), data.isSelected(wInner));
}
if(wayStyle.equals(innerStyle))
{
putError(r, tr("Style for inner way ''{0}'' equals multipolygon.",
wInner.getDisplayName(DefaultNameFormatter.getInstance())), false);
if(!data.isSelected(r)) {
wInner.mappaintDrawnAreaCode = paintid;
}
}
}
}
for (Way wOuter : multipolygon.getOuterWays())
{
ElemStyle outerStyle = getPrimitiveStyle(wOuter);
if(outerStyle == null)
{
// Selected ways are drawn at the very end
if (data.isSelected(wOuter)) {
continue;
}
if(zoomok)
{
((AreaElemStyle)wayStyle).getLineStyle().paintPrimitive(wOuter, paintSettings, painter, (data.isSelected(wOuter) || data.isSelected(r)));
}
wOuter.mappaintDrawnCode = paintid;
}
else
{
if(outerStyle instanceof AreaElemStyle
&& !wayStyle.equals(outerStyle))
{
putError(r, tr("Style for outer way ''{0}'' mismatches.",
wOuter.getDisplayName(DefaultNameFormatter.getInstance())), true);
}
if(data.isSelected(r))
{
drawSelectedMember(wOuter, outerStyle, false, false);
}
else if(outerStyle instanceof AreaElemStyle) {
wOuter.mappaintDrawnAreaCode = paintid;
}
}
}
}
return drawn;
}
protected boolean isPolygonVisible(Polygon polygon) {
Rectangle bounds = polygon.getBounds();
if (bounds.width == 0 && bounds.height == 0) return false;
if (bounds.x > nc.getWidth()) return false;
if (bounds.y > nc.getHeight()) return false;
if (bounds.x + bounds.width < 0) return false;
if (bounds.y + bounds.height < 0) return false;
return true;
}
protected Polygon getPolygon(Way w)
{
Polygon polygon = new Polygon();
for (Node n : w.getNodes())
{
Point p = nc.getPoint(n);
polygon.addPoint(p.x,p.y);
}
return polygon;
}
protected Point2D getCentroid(Polygon p)
{
double cx = 0.0, cy = 0.0, a = 0.0;
// usually requires points[0] == points[npoints] and can then use i+1 instead of j.
// Faked it slowly using j. If this is really gets used, this should be fixed.
for (int i = 0; i < p.npoints; i++) {
int j = i+1 == p.npoints ? 0 : i+1;
a += (p.xpoints[i] * p.ypoints[j]) - (p.ypoints[i] * p.xpoints[j]);
cx += (p.xpoints[i] + p.xpoints[j]) * (p.xpoints[i] * p.ypoints[j] - p.ypoints[i] * p.xpoints[j]);
cy += (p.ypoints[i] + p.ypoints[j]) * (p.xpoints[i] * p.ypoints[j] - p.ypoints[i] * p.xpoints[j]);
}
return new Point2D.Double(cx / (3.0*a), cy / (3.0*a));
}
protected double getArea(Polygon p)
{
double sum = 0.0;
// usually requires points[0] == points[npoints] and can then use i+1 instead of j.
// Faked it slowly using j. If this is really gets used, this should be fixed.
for (int i = 0; i < p.npoints; i++) {
int j = i+1 == p.npoints ? 0 : i+1;
sum = sum + (p.xpoints[i] * p.ypoints[j]) - (p.ypoints[i] * p.xpoints[j]);
}
return Math.abs(sum/2.0);
}
DataSet data;
<T extends OsmPrimitive> Collection<T> selectedLast(final DataSet data, Collection <T> prims) {
ArrayList<T> sorted = new ArrayList<T>(prims);
Collections.sort(sorted,
new Comparator<T>() {
public int compare(T o1, T o2) {
boolean s1 = data.isSelected(o1);
boolean s2 = data.isSelected(o2);
if (s1 && !s2)
return 1;
if (!s1 && s2)
return -1;
return o1.compareTo(o2);
}
});
return sorted;
}
/* Shows areas before non-areas */
public void visitAll(DataSet data, boolean virtual, Bounds bounds) {
BBox bbox = new BBox(bounds);
this.data = data;
++paintid;
useStyleCache = Main.pref.getBoolean("mappaint.cache", true);
int fillAreas = Main.pref.getInteger("mappaint.fillareas", 10000000);
LatLon ll1 = nc.getLatLon(0, 0);
LatLon ll2 = nc.getLatLon(100, 0);
dist = ll1.greatCircleDistance(ll2);
zoomLevelDisplay = Main.pref.getBoolean("mappaint.zoomLevelDisplay", false);
circum = nc.getDist100Pixel();
styles = MapPaintStyles.getStyles().getStyleSet();
drawMultipolygon = Main.pref.getBoolean("mappaint.multipolygon", true);
drawRestriction = Main.pref.getBoolean("mappaint.restriction", true);
leftHandTraffic = Main.pref.getBoolean("mappaint.lefthandtraffic", false);
minEN = nc.getEastNorth(0, nc.getHeight() - 1);
maxEN = nc.getEastNorth(nc.getWidth() - 1, 0);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
Main.pref.getBoolean("mappaint.use-antialiasing", false) ?
RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
this.paintSettings = MapPaintSettings.INSTANCE;
this.painter = new MapPainter(paintSettings, g, inactive, nc, virtual, dist, circum);
data.clearErrors();
if (fillAreas > dist && styles != null && styles.hasAreas()) {
Collection<Way> noAreaWays = new LinkedList<Way>();
/*** RELATIONS ***/
for (final Relation osm: data.searchRelations(bbox)) {
if (osm.isDrawable()) {
paintUnselectedRelation(osm);
}
}
/*** AREAS ***/
for (final Way osm : selectedLast(data, data.searchWays(bbox))) {
if (osm.isDrawable() && osm.mappaintDrawnCode != paintid) {
if (isPrimitiveArea(osm) && osm.mappaintDrawnAreaCode != paintid) {
drawWay(osm, fillAreas);
} else {
noAreaWays.add(osm);
}
}
}
/*** WAYS ***/
for (final Way osm : noAreaWays) {
drawWay(osm, 0);
}
} else {
drawMultipolygon = false;
/*** RELATIONS ***/
for (final Relation osm: data.searchRelations(bbox)) {
if (osm.isDrawable()) {
paintUnselectedRelation(osm);
}
}
/*** WAYS (filling disabled) ***/
for (final Way way: data.searchWays(bbox)) {
if (way.isDrawable() && !data.isSelected(way)) {
drawWay(way, 0);
}
}
}
/*** SELECTED ***/
for (final OsmPrimitive osm : data.getSelected()) {
if (osm.isUsable() && !(osm instanceof Node) && osm.mappaintDrawnCode != paintid) {
osm.visit(new AbstractVisitor() {
public void visit(Way w) {
drawWay(w, 0);
}
public void visit(Node n) {
// Selected nodes are painted in following part
}
public void visit(Relation r) {
/* TODO: is it possible to do this like the nodes/ways code? */
// Only nodes are painted, ways was already painted before (this might cause that
// way in selected relation is hidden by another way)
for (RelationMember m : r.getMembers()) {
if (m.isNode() && m.getMember().isDrawable()) {
drawSelectedMember(m.getMember(), styles != null ? getPrimitiveStyle(m.getMember()) : null, true, true);
}
}
}
});
}
}
/*** NODES ***/
for (final Node osm: data.searchNodes(bbox)) {
if (!osm.isIncomplete() && !osm.isDeleted() && (data.isSelected(osm) || !osm.isFiltered())
&& osm.mappaintDrawnCode != paintid) {
drawNode(osm);
}
}
painter.drawVirtualNodes(data.searchWays(bbox));
}
public void putError(OsmPrimitive p, String text, boolean isError)
{
data.addError(p, isError ? tr("Error: {0}", text) : tr("Warning: {0}", text));
}
public void setGraphics(Graphics2D g) {
this.g = g;
}
public void setInactive(boolean inactive) {
this.inactive = inactive;
}
public void setNavigatableComponent(NavigatableComponent nc) {
this.nc = nc;
}
}