/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package tufts.vue;
import tufts.vue.gui.TextRow;
import tufts.vue.ui.ResourceIcon;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import javax.swing.*;
import javax.swing.border.*;
import com.lowagie.text.pdf.PdfGraphics2D;
import edu.tufts.vue.preferences.PreferencesManager;
import java.util.Iterator;
import edu.tufts.vue.preferences.PreferencesManager;
import edu.tufts.vue.preferences.VuePrefEvent;
import edu.tufts.vue.preferences.VuePrefListener;
import edu.tufts.vue.preferences.implementations.BooleanPreference;
import edu.tufts.vue.preferences.implementations.ShowIconsPreference;
import edu.tufts.vue.preferences.interfaces.VuePreference;
/**
* Icon's for displaying on LWComponents.
*
* Various icons can be displayed and stacked vertically or horizontally.
* The icon region displays a tool-tip on rollover and may handle double-click.
*
* @version $Revision: 1.71 $ / $Date: 2007/11/22 07:28:50 $ / $Author: sfraize $
*
*/
public abstract class LWIcon extends Rectangle2D.Float
implements VueConstants
{
private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(LWIcon.class);
private static final float DefaultScale = 0.045f; // scale to apply to the absolute size of our vector based icons
private static final Color TextColor = VueResources.getColor("node.icon.color.foreground");
private static final Color FillColor = VueResources.getColor("node.icon.color.fill");
public static final Font FONT_ICON = VueResources.getFont("node.icon.font");
private final static BooleanPreference oneClickLaunchResPref = BooleanPreference.create(
edu.tufts.vue.preferences.PreferenceConstants.INTERACTIONS_CATEGORY,
"oneClickResLaunch",
VueResources.getString("preference.resourcelaunching.title"),
VueResources.getString("preference.resourcelaunching.description"),
Boolean.TRUE,
true);
protected LWComponent mLWC;
protected final Color mColor;
protected float mMinWidth;
protected float mMinHeight;
/** the cached value of isWanted for isShowing */
protected boolean isLaidOut;
/** Note that quering the preference object for its boolean state values is a slow call. This
* used to be a problem as we polled it constantly while map drawing and when the mouse was
* moving over objects, tho this has been fixed with that addition of isWanted in the LWIcon
* API, and by caching it to isLaidOut/isShowing() when a layout happens (and VUE has been
* fixed elsewhere to make sure it always does layouts at the needed times -- e.g., newly
* constructed maps must do a layout pass before any icons will appear). A single listener for
* all of VUE is now added to this preference in a static init block, which will re-layout all
* open maps whenever a change is detected. **/
static final ShowIconsPreference IconPref = new ShowIconsPreference();
// The above polling of the old isShowing() happened to also be made much worse by a crazy
// implemention detail of MetadataList, which would construct an entire HTML description of
// each type of meta-data and check to see if it was empty in order to tell us if any meta-data
// for that type was present at all, and this was being called with the same frequency
// described above.
static {
// Preferences used to be very slow to open when there were lots of nodes in the VUE
// runtime -- the problem showed up here. (1) ShowIconsPreference was generating a
// spurious change event for every checkbox in the UI whenever the pref dialog was opened
// [FIXED]. (2) The PreferencesDialog wasn't cached -- a new one was created every time
// [FIXED]. This was resulting in one all-node/all-link in all-maps re-layout per
// preference change per time that a the preference dialog had been opened -- so it would
// get slower, and slower. And on preference dialog open, multiple THAT number by 5 -- the
// number of checkboxes in the UI. Also, *every single node in existence* used to be a
// listener, (that was crazy), so it was generating a zillion events. Now, this is the
// single lister in all of VUE for icon preference changes. [SMF -- summer 2012]
IconPref.addVuePrefListener(new VuePrefListener() {
public void preferenceChanged(VuePrefEvent pe) {
// Log.info("icon prefs change notification: " + tufts.Util.tags(pe));
// if (pe.getNewValue().equals(pe.getOldValue())) // Can't rely on this: values not 100% reliable
// return;
Log.info("an icon display preference has changed: re-layout of all open VUE maps");
final LWMap activeMap = VUE.getActiveMap();
// refresh the active map immediatley, and other maps interspersed on AWT thread
if (activeMap != null) {
refreshIconsForMap(activeMap);
activeMap.notify(LWKey.Repaint); // todo: shouldn't need this for MapPanner updates
}
for (final LWMap map : VUE.getAllMaps()) {
if (map != activeMap) {
tufts.vue.gui.GUI.invokeAfterAWT(new Runnable() { public void run() {
refreshIconsForMap(map);
}});
}
}
//if (DEBUG.Enabled) Log.info("all maps re-laid out.");
}
});
}
private static void refreshIconsForMap(final LWMap map) {
if (DEBUG.Enabled) Log.info("layout " + map);
final UndoManager undo = map.getUndoManager();
undo.setSuspended(true);
try {
map.layoutAll(LWIcon.class);
} catch (Throwable t) {
Log.warn("layout error", t);
} finally {
undo.setSuspended(false);
}
// Bizzare issue: if there was an item in an LWGroup that had an icon state change, all
// visual map updates were stopping until the preferences were dismissed, when the current
// state was finally displayed. This has to do with group normalization: when a group
// changes, it will issue a DisperseOrNormalize cleanup task. The MapViewer skips all
// repaints while there are outstanding cleanup tasks, as that is considered an
// "intermediate state". Recording an undo mark (even tho there will be no events /
// nothing to record as events were suspended), will cause all outstanding cleanup tasks to
// run. And we should be doing an undo mark here for good measure in any case: if somehow
// an event ever *were* to get through, better to create as a separate undo instead of
// polluting some other undo. Note that since we've added UndoManager.setSuspended,
// "something getting through" should be impossible.
undo.mark();
}
private LWIcon(LWComponent lwc, Color c) {
// default size
super.width = 22;
super.height = 12;
mLWC = lwc;
mColor = c;
}
private LWIcon(LWComponent lwc) {
this(lwc, TextColor);
}
// public static ShowIconsPreference getShowIconPreference()
// {
// return IconPref;
// }
public void setLocation(float x, float y)
{
super.x = x;
super.y = y;
}
public void setSize(float w, float h)
{
super.width = w;
super.height = h;
}
/**
* do we contain coords x,y?
* Coords may be component local or map local or
* whataver -- depends on what was handed to us
* via @see setLocation
*/
public boolean contains(float x, float y)
{
if (isShowing() && super.width > 0 && super.height > 0) {
return x >= super.x+1
&& y >= super.y+1
&& x <= (super.x + super.width -1)
&& y <= (super.y + super.height -1);
}
return false;
}
public void setMinimumSize(float w, float h)
{
mMinWidth = w;
mMinHeight = h;
if (super.width < w)
super.width = w;
if (super.height < h)
super.height = h;
}
// public void setColor(Color c) {
// mColor = c;
// }
void draw(DrawContext dc)
{
if (DEBUG.BOXES) {
dc.g.setColor(Color.red);
dc.g.setStroke(STROKE_SIXTEENTH);
dc.g.draw(this);
}
}
void layout() {} // subclasses can check for changes in here
final boolean isShowing() {
return isLaidOut;
}
abstract boolean isWanted();
abstract void doSingleClickAction();
abstract void doDoubleClickAction();
abstract public JComponent getToolTipComponent();
//todo: make getToolTipComponent static & take lwc arg in case anyone else wants these
//=============================================================================
//=============================================================================
// This is a TOTAL hack for a last minute change to VUE 2.0.1 2008-04-15 -- SMF
//-----------------------------------------------------------------------------
private static final Cursor RESOURCE_CURSOR = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
// public static final Cursor RESOURCE_CURSOR = new Cursor(Cursor.HAND_CURSOR) {
// public String getName() { return "VUE-RESOURCE-HAND"; }};
private static tufts.vue.Resource RolloverResource;
public static boolean hasRolloverResource(Cursor cursor) {
return RolloverResource != null && cursor == RESOURCE_CURSOR;
}
public static void clearRolloverResource() {
RolloverResource = null;
final MapViewer viewer = VUE.getActiveViewer();
if (viewer != null && viewer.getCursor() == RESOURCE_CURSOR)
{
viewer.setTopCursor(VueToolbarController.getActiveTool().getCursor());
}
}
public static void displayRolloverResource() {
if (RolloverResource != null) {
// uses failsafe operation to reduce buggy side effecs of this hack: only
// first attempt works, then we auto-clear the hacked up global state that
// says "a resource icon is currently active to override all single clicks
// on the map" until mouse moves over resource icon region again to be
// reconfirmed as a viable option.
if (oneClickLaunchResPref.isTrue())
RolloverResource.displayContent();
clearRolloverResource();
}
}
//=============================================================================
//=============================================================================
public static class Block extends Rectangle2D.Float
{
public static final boolean VERTICAL = true;
public static final boolean HORIZONTAL = false;
//public static final int COORDINATES_MAP = 0;
//public static final int COORDINATES_COMPONENT = 1;
//public static final int COORDINATES_COMPONENT_NO_SHRINK = 2; // only currently works for blocks laid out at 0,0 of node
private LWComponent mLWC;
private final LWIcon[] mIcons = new LWIcon[7];
//final private boolean mCoordsNodeLocal;
//final private boolean mCoordsNoShrink; // don't let icon's get less than 100% zoom
final private boolean mNoShrink; // don't let icon's get less than 100% zoom
private boolean mVertical;
private final float mIconWidth;
private final float mIconHeight;
public Block(LWComponent lwc,
int iconWidth,
int iconHeight,
Color c,
boolean vertical)
//boolean noShrink)
//int coordStyle)
{
if (c == null)
c = TextColor;
//mCoordsNodeLocal = (coordStyle >= COORDINATES_COMPONENT);
//mCoordsNoShrink = (coordStyle == COORDINATES_COMPONENT_NO_SHRINK);
mNoShrink = (lwc instanceof LWImage);
mIconWidth = iconWidth;
mIconHeight = iconHeight;
setOrientation(vertical);
// todo: create these lazily
mIcons[0] = new LWIcon.Resource(lwc, c);
// mIcons[1] = new LWIcon.Behavior(lwc, c);
mIcons[1] = new LWIcon.Notes(lwc, c);
mIcons[2] = new LWIcon.Pathway(lwc, c);
mIcons[3] = new LWIcon.MetaData(lwc, c);
mIcons[4] = new LWIcon.Hierarchy(lwc, c);
mIcons[5] = new LWIcon.MergeSourceMetaData(lwc,c);
mIcons[6] = new LWIcon.OntologicalMetaData(lwc,c);
for (int i = 0; i < mIcons.length; i++) {
mIcons[i].setSize(iconWidth, iconHeight);
mIcons[i].setMinimumSize(iconWidth, iconHeight);
}
this.mLWC = lwc;
// FIXED: it was way WAY too heavy weight to have every node and every link made a
// listener for this preference. Is now handled in our static singleton listener. This
// is why the preferences pane use to be so slow to appear -- and the more maps open,
// the slower it would be. And because of a horrible MetadataList hack, tons of
// meta-data html descriptions ended up being constructed for ever map paint!
// IconPref.addVuePrefListener(new VuePrefListener() { public void
// preferenceChanged(VuePrefEvent prefEvent) { mLWC.layout(); } });
}
public float getIconWidth() { return mIconWidth; }
public float getIconHeight() { return mIconHeight; }
public void setOrientation(boolean vertical)
{
mVertical = vertical;
if (vertical)
super.width = mIconWidth;
else
super.height = mIconHeight;
}
/**
* do we contain coords x,y?
* Coords may be component local or map local or
* whataver -- depends on what was handed to us
* via @see setLocation
*/
public boolean contains(float x, float y)
{
if (isShowing() && super.width > 0 && super.height > 0) {
return x >= super.x
&& y >= super.y
&& x <= (super.x + super.width)
&& y <= (super.y + super.height);
}
return false;
}
public String toString()
{
return "LWIcon.Block[" + super.x+","+super.y + " " + super.width+"x"+super.height + " " + mLWC + "]";
}
//public float getWidth() { return super.width; }
//public float getHeight() { return super.height; }
boolean isShowing() {
return super.width > 0 && super.height > 0;
}
void setLocation(float x, float y)
{
super.x = x;
super.y = y;
layout();
}
/** Layout whatever is currently relevant to show, computing
* width & height -- does NOT change location of the block itself
*/
void layout()
{
if (mVertical) {
super.height = 0;
float iconY = super.y;
for (LWIcon icon : mIcons) {
if (icon.isWanted()) {
icon.layout();
icon.setLocation(x, iconY);
iconY += icon.height+3;
super.height += icon.height+3;
icon.isLaidOut = true;
} else {
icon.isLaidOut = false;
}
}
} else {
super.width = 0;
float iconX = super.x;
for (LWIcon icon : mIcons) {
if (icon.isWanted()) {
icon.layout();
icon.setLocation(iconX, y);
iconX += icon.width+3;
super.width += icon.width+3;
icon.isLaidOut = true;
} else {
icon.isLaidOut = false;
}
}
}
}
void draw(DrawContext dc)
{
// If mCoordsNoShrink is true, never let icon size get less than 100% for images (tho
// also it shouldn't be allowed BIGGER than the object...) This is experimental in
// that it only currently works if the block is laid out at 0,0, because we scale the
// DrawContext once at the top here, which will offset any non-zero locations (and we
// don't want the location changed, only the size). You you place the block at > 0,0,
// the icons will be moved outside the node when the scale gets small enough.
// Also, if the scale becomes VERY small, the icon block will be drawn bigger than the
// image itself. TODO: fix all the above or handle this some other way.
if (mNoShrink && dc.zoom < 1)
dc.setAbsoluteDrawing(true);
for (LWIcon icon : mIcons)
if (icon.isShowing())
icon.draw(dc);
if (mNoShrink && dc.zoom < 1)
dc.setAbsoluteDrawing(false);
}
void checkAndHandleMouseOver(MapMouseEvent e)
{
final Point2D.Float localPoint = e.getLocalPoint(mLWC);
float cx = localPoint.x;
float cy = localPoint.y;
RolloverResource = null;
JComponent tipComponent = null;
LWIcon tipIcon = null;
for (LWIcon icon : mIcons) {
if (!icon.isShowing())
continue;
if (mNoShrink) {
// TODO: this probably no longer quite right given local coords...
double zoom = e.getViewer().getZoomFactor();
if (zoom < 1) {
cx *= zoom;
cy *= zoom;
}
}
if (icon instanceof LWIcon.Resource) {
if (icon.contains(cx, cy)) {
// e.getViewer().clearTip();
// TODO: need cursor management system for MapViewer's that allows
// us to install a temporary cursor that's auto-cleared if
// mouse exits or re-enters the viewer
e.getViewer().setTopCursor(RESOURCE_CURSOR);
RolloverResource = icon.mLWC.getResource(); // hack hack hack
} else {
e.getViewer().setTopCursor(VueToolbarController.getActiveTool().getCursor());
}
}
if (icon.contains(cx, cy)) {
tipIcon = icon;
break;
} else {
// check: if (above condition) then: break else: reset cursor, but at the bottom of a loop?
e.getViewer().setTopCursor(VueToolbarController.getActiveTool().getCursor());
}
}
// TODO: don't need to do this if there's already a tip showing!
if (tipIcon != null) {
tipComponent = tipIcon.getToolTipComponent();
final Rectangle2D tipRegion = mLWC.transformZeroToMapRect((Rectangle2D.Float) tipIcon.getBounds2D());
// if node, compute avoid region node+tipRegion,
// if link avoid = label+entire tip block
final Rectangle2D avoidRegion;
if (mLWC instanceof LWLink) {
if (mLWC.hasLabel()) {
avoidRegion = new Rectangle2D.Float();
// Stay away from the link label:
avoidRegion.setRect(mLWC.labelBox.getBoxBounds());
// Also stay way from the whole icon block:
avoidRegion.add(this);
} else {
// Just stay way from the whole icon block:
avoidRegion = this;
}
mLWC.transformZeroToMapRect((Rectangle2D.Float)avoidRegion);
} else {
// So it works when zoomed focus, use the transform:
avoidRegion = mLWC.transformZeroToMapRect(mLWC.getZeroBounds());
//avoidRegion = mLWC.getBounds();
}
//if (!(tipIcon instanceof LWIcon.Resource))
// {
e.getViewer().activateRolloverToolTip(e, tipComponent, avoidRegion, tipRegion);
// }
}
}
boolean handleSingleClick(MapMouseEvent e)
{
boolean handled = false;
final Point2D.Float localPoint = e.getLocalPoint(mLWC);
for (int i = 0; i < mIcons.length; i++) {
LWIcon icon = mIcons[i];
if (icon.isShowing() && icon.contains(localPoint.x, localPoint.y)) {
icon.doSingleClickAction();
handled = true;
break;
}
}
return handled;
}
boolean handleDoubleClick(MapMouseEvent e)
{
boolean handled = false;
final Point2D.Float localPoint = e.getLocalPoint(mLWC);
for (int i = 0; i < mIcons.length; i++) {
LWIcon icon = mIcons[i];
if (icon.isShowing() && icon.contains(localPoint.x, localPoint.y)) {
icon.doDoubleClickAction();
handled = true;
break;
}
}
return handled;
}
}
/**
* AALabel: A JLabel that forces anti-aliasing -- use this if
* you want a tool-tip to be anti-aliased on the PC,
* because there's no way to set it otherwise.
* (This is redundant on the Mac which does it automatically)
*/
class AALabel extends JLabel
{
AALabel(String s) { super(s); };
public void paintComponent(Graphics g) {
((Graphics2D)g).setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
super.paintComponent(g);
}
}
static class Resource extends LWIcon
{
private final static String NoResource = VueUtil.isMacPlatform() ? "---" : "__";
// On PC, two underscores look better than "---" in default Trebuchet font,
// which leaves the dashes high in the box.
private TextRow mTextRow;
private String extension;
private final Rectangle2D.Float boxBounds = new Rectangle2D.Float();
Resource(LWComponent lwc) { super(lwc); }
Resource(LWComponent lwc, Color c) {
super(lwc, c);
layout();
}
boolean isWanted() {
return IconPref.getResourceIconValue() && mLWC.hasResource();
}
// void doDoubleClickAction() {}
void doDoubleClickAction() {
if (oneClickLaunchResPref.isFalse())
{
Log.debug("DOUBLE-CLICK " + getClass());
mLWC.getResource().displayContent();
}
}
void doSingleClickAction() {
if (oneClickLaunchResPref.isTrue())
{
Log.debug("SINGLE CLICK " + getClass() + " " + mLWC);
mLWC.getResource().displayContent();
}
}
final static String gap = " ";
final static String indent = "";
//final static String indent = " ";
private JLabel ttResource;
public JComponent getToolTipComponent()
{
if (ttResource == null) {
ttResource = new AALabel("");
//label.setFont(FONT_MEDIUM);
// todo: "Arial Unicode MS" looks great on mac (which it maps to Hevetica, which on
// mac supports unicode, and appears identical to Arial), but is only thing that
// works for unicode on the PC, yet looks relaively crappy for regular text. Check
// into fonts avail on Win XP (this was Win2k) todo: try embedding the font name in
// the HTML above for just the title, and not the URL & click message.
ttResource.setFont(FONT_MEDIUM_UNICODE);
}
final tufts.vue.Resource r = mLWC.getResource();
final boolean hasTitle = (r.getTitle() != null && !r.getTitle().equals(r.getLocationName()));
final String prettyResource = r.getLocationName();
ttResource.setIcon(r.getTinyIcon());
ttResource.setVerticalTextPosition(SwingConstants.TOP);
ttResource.setText(
"<html>"
+ (hasTitle ? (indent + r.getTitle() + " <br>") : "")
+ indent + prettyResource// + gap
//+ "<font size=-2 color=#999999><br>" + indent + "Double-click to open in new window "
);
// final tufts.vue.Resource r = mLWC.getResource();
// final boolean hasTitle = (r.getTitle() != null && !r.getTitle().equals(r.getSpec()));
// final String prettyResource = r.getSpec();
// ttResource.setIcon(r.getTinyIcon());
// ttResource.setVerticalTextPosition(SwingConstants.TOP);
// // already has a border -- either make compound or put in a panel
// // if (DEBUG.BOXES)
// // ttResource.setBorder(new LineBorder(Color.green, 1));
// // else
// // ttResource.setBorder(BorderFactory.createEmptyBorder(1,1,0,1));
// ttResource.setText(
// "<html>"
// + (hasTitle ? (indent + r.getTitle() + " <br>") : "")
// + indent + prettyResource// + gap
// //+ "<font size=-2 color=#999999><br>" + indent + "Double-click to open in new window "
// );
return ttResource;
}
// private JComponent ttResource;
// private String ttLastString;
// private boolean hadTitle = false;
// private long lastAccess = 0;
// public JComponent getToolTipComponent()
// {
// tufts.vue.Resource r = mLWC.getResource();
// boolean hasTitle = (r.getTitle() != null && !r.getTitle().equals(r.getSpec()));
// long access = 0;
// if (r instanceof URLResource)
// access = ((URLResource)r).getAccessSuccessful();
// if (ttResource == null
// || access > lastAccess // title may have been updated
// || !ttLastString.equals(mLWC.getResource().getSpec())
// || hadTitle != hasTitle)
// {
// hadTitle = hasTitle;
// ttLastString = mLWC.getResource().getSpec();
// // todo perf: use StringBuffer
// String prettyURL = VueUtil.decodeURL(ttLastString);
// if (prettyURL.startsWith("file://") && prettyURL.length() > 7)
// prettyURL = prettyURL.substring(7);
// final String html =
// "<html>"
// + (hasTitle ? (gap + "<b>"+mLWC.getResource().getTitle()+"</b> <br>") : "")
// + gap + prettyURL + gap
// //+ (hasTitle?("<font size=-2><br> "+mLWC.getResource().getTitle()+"</font>"):"")
// + "<font size=-2 color=#999999><br> Double-click to open in new window "
// ;
// JLabel label = new AALabel(html);
// //label.setFont(FONT_MEDIUM);
// // todo: "Arial Unicode MS" looks great on mac (which it maps to Hevetica, which on
// // mac supports unicode, and appears identical to Arial), but is only thing that
// // works for unicode on the PC, yet looks relaively crappy for regular text. Check
// // into fonts avail on Win XP (this was Win2k) todo: try embedding the font name in
// // the HTML above for just the title, and not the URL & click message.
// label.setFont(FONT_MEDIUM_UNICODE);
// if (false) {
// // example of button a gui component in a rollover
// JPanel panel = new JPanel();
// panel.setName("LWIcon$Resource-Action");
// panel.add(label);
// //JButton btn = new JButton(new VueAction(prettyURL) { // looks okay but funny & leaves out title...
// //JButton btn = new JButton(new VueAction(html) { // looks terrible
// AbstractButton btn = new JButton(new VueAction("Open") {
// // TODO: need a superclass of VueAction that doesn't add it to a global list,
// // as this action is very transient, and we'll want it GC'able. And actually,
// // in this case, the resource object itself ought to have an action built in
// // that can be re-used by everyone interested in doing this.
// public void act() { doDoubleClickAction(); }
// });
// btn.setOpaque(false);
// btn.setToolTipText(null);
// btn.setFont(FONT_SMALL_BOLD);
// panel.add(btn);
// //panel.setBackground(Color.white); // no effect
// ttResource = panel;
// } else {
// ttResource = label;
// }
// }
// lastAccess = access;
// return ttResource;
// }
// void draw(DrawContext dc)
// {
// super.draw(dc);
// if (mLWC.hasResource()) {
// // Draw a small image icon instead of the text "extension" icon
// final Image image;
// if (!dc.isInteractive() || dc.getAbsoluteScale() >= 2) {
// // non-interative: eg, printing or image generating
// image = mLWC.getResource().getLargeIconImage();
// } else
// image = mLWC.getResource().getTinyIconImage();
// if (image != null) {
// final double iw = image.getWidth(null);
// final double ih = image.getHeight(null);
// final AffineTransform tx = AffineTransform.getTranslateInstance(getX() + (getWidth() - 16) / 2,
// getY() + (getHeight() - 16) / 2);
// //final AffineTransform tx = AffineTransform.getScaleInstance(1.0/8.0, 1.0/8.0);
// //final AffineTransform tx = new AffineTransform();
// if (iw > 16)
// tx.scale(16 / iw, 16 / iw);
// //tx.scale(1.0/8.0, 1.0/8.0);
// dc.g.drawImage(image, tx, null);
// //return;
// }
// }
// if (mTextRow == null)
// return;
// double _x = getX();
// double _y = getY();
// dc.g.translate(_x, _y);
// dc.g.setColor(mColor);
// dc.g.setFont(FONT_ICON);
// float xoff = (super.width - mTextRow.width) / 2;
// float yoff = (super.height - mTextRow.height) / 2;
// mTextRow.draw(dc.g, xoff, yoff);
// // an experiment in semantic zoom
// // (SansSerif point size 1 MinisculeFont get's garbled on mac, so we don't do it there)
// if (!VueUtil.isMacPlatform() && mLWC.hasResource() && dc.g.getTransform().getScaleX() >= 8.0) {
// dc.g.setFont(MinisculeFont);
// dc.g.setColor(Color.gray);
// dc.g.drawString(mLWC.getResource().toString(), 0, (int)(super.height));
// }
// dc.g.translate(-x, -y);
// }
void layout() {
extension = null;
internalLayout();
}
//private static final Color BoxFill = new Color(238, 238, 238);
private static final Color BoxFill = FillColor;
//private static final Color BoxBorder = new Color(149, 149, 149);
void internalLayout()
{
if (extension == null) {
if (mLWC.hasResource())
extension = mLWC.getResource().getDataType();
if (extension == null || extension.length() < 1) {
extension = NoResource;
} else if (extension.length() > 3) {
extension = extension.substring(0,3);
}
// if (DEBUG.RESOURCE) {
// if (mLWC.hasResource())
// Log.debug(" " + mLWC.getResource() + "; yielded extension ["+extension+"]");
// else if (DEBUG.META)
// Log.debug(mLWC + "; no resource; extension = ["+extension+"]");
// }
mTextRow = TextRow.instance(extension.toLowerCase(), FONT_ICON);
}
// todo: should only have to do this once, but we need to fix init
// so that this doesn't get called till Rectangle2D.this is fully positioned
boxBounds.setRect(this);
if (mLWC instanceof LWNode) {
final float insetW = 2;
final float insetH = 1;
boxBounds.x += insetW;
boxBounds.y += insetH;
boxBounds.width -= insetW * 2;
boxBounds.height -= insetH * 2;
}
// // Resource icon special case can override parent set width:
// super.width = mTextRow.width;
// if (super.width < super.mMinWidth)
// super.width = super.mMinWidth;
}
void draw(DrawContext dc)
{
if (false&&DEBUG.Enabled) {
// test code for inserting actual icon into node icon gutter (should at least do by
// default for local file icons)
Icon icon = mLWC.getResource().getContentIcon();
if (icon instanceof ResourceIcon) {
((ResourceIcon)icon).setSize((int)Math.round(getWidth()-4),(int)Math.round(getHeight()));
icon.paintIcon(null, dc.g, (int)Math.round(getX()+2), (int)Math.round(getY()+2));
super.draw(dc);
return;
}
}
if (true || extension == null) // TODO PERF: sometimes starts with boxBounds wrong...
internalLayout();
super.draw(dc);
if (dc.isIndicated(mLWC)) {
dc.g.setColor(Color.white);
dc.g.fill(boxBounds);
dc.g.setColor(Color.gray);
dc.g.setStroke(STROKE_HALF);
dc.g.draw(boxBounds);
// Leave an empty box:
return;
}
// System.out.println("CHECK PDF RENDERING");
if (!dc.isInteractive())
{
// System.out.println("PDF RENDERING");
dc.g.setRenderingHint(PdfGraphics2D.HyperLinkKey.KEY_INSTANCE,
mLWC.getResource().getSpec());
BufferedImage bi = new BufferedImage((int)getWidth(),(int)getHeight(),BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.createGraphics();
if( g instanceof Graphics2D ){
Graphics2D g2d = (Graphics2D) g;
// make sure the background is filled with transparent pixels when cleared !
g2d.setBackground(new Color(0,0,0,0));
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, 0.0f));
g2d.clearRect(0,0, (int)getWidth(), (int)getHeight());
}
dc.g.drawImage(bi, (BufferedImageOp)null, (int)getX(), (int)getY());
dc.g.setRenderingHint(PdfGraphics2D.HyperLinkKey.KEY_INSTANCE,
PdfGraphics2D.HyperLinkKey.VALUE_HYPERLINKKEY_OFF);
}
// TODO PERF: if BoxFill has alpha, pre-mix it with node.getRenderFillColor()
final Color fill = mLWC.getRenderFillColor(dc); // todo: getContrastColor(dc)
// if (mLWC instanceof LWLink)
// dc.g.setColor(fill);
// else
dc.g.setColor(BoxFill);
dc.g.fill(boxBounds);
//dc.g.setColor(BoxBorder);
dc.g.setColor(fill == null ? Color.gray : fill.darker());
dc.g.setStroke(STROKE_HALF);
dc.g.draw(boxBounds);
dc.g.setColor(mColor);
dc.g.setFont(FONT_ICON);
// String extension = NoResource;
// if (mLWC.hasResource())
// extension = mLWC.getResource().getExtension();
double x = getX();
double y = getY();
dc.g.translate(x, y);
// todo perf: listen for resource change & cache text row
//TextRow row = new TextRow(extension, dc.g);
final TextRow row = mTextRow;
// Resource icon special case can override parent set width:
if (super.width < row.width)
super.width = row.width;
final float xoff = (super.width - row.width) / 2;
final float yoff = (super.height - row.height) / 2;
row.draw(dc, xoff, yoff);
// // an experiment in semantic zoom
// //if (dc.zoom >= 8.0 && mLWC.hasResource()) {
// if (mLWC.hasResource() && dc.g.getTransform().getScaleX() >= 8.0) {
// dc.g.setFont(MinisculeFont);
// dc.g.setColor(Color.gray);
// dc.g.drawString(mLWC.getResource().toString(), 0, (int)(super.height));
// }
dc.g.translate(-x, -y);
//MK System.out.println("K");
}
}
static class Notes extends LWIcon
{
private final static float MaxX = 155;
private final static float MaxY = 212;
private final static float scale = DefaultScale;
private final static AffineTransform t = AffineTransform.getScaleInstance(scale, scale);
private final static Stroke stroke = new BasicStroke(0.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
static float iconWidth = MaxX * scale;
static float iconHeight = MaxY * scale;
//-------------------------------------------------------
private final static GeneralPath pencil_body = new GeneralPath();
private final static GeneralPath pencil_point = new GeneralPath();
private final static GeneralPath pencil_tip = new GeneralPath();
//static float iconXoff = (super.width - iconWidth) / 2f;
//static float iconYoff = (super.height - iconHeight) / 2f;
static {
pencil_body.moveTo(0,31);
pencil_body.lineTo(55,0);
pencil_body.lineTo(150,155);
pencil_body.lineTo(98,187);
pencil_body.closePath();
pencil_body.transform(t);
pencil_point.moveTo(98,187);
pencil_point.lineTo(150,155);
pencil_point.lineTo(150,212);
pencil_point.closePath();
/*pencil_point.moveTo(150,155);
pencil_point.lineTo(150,212);
pencil_point.lineTo(98,187);
*/
pencil_point.transform(t);
pencil_tip.moveTo(132,203);
pencil_tip.lineTo(150,192);
pencil_tip.lineTo(150,212);
pencil_tip.closePath();
pencil_tip.transform(t);
}
Notes(LWComponent lwc, Color c) { super(lwc, c); }
Notes(LWComponent lwc) { super(lwc); }
boolean isWanted() {
return IconPref.getNotesIconValue() && mLWC.hasNotes();
}
void doSingleClickAction() {}
void doDoubleClickAction() {
// is it faster to use Method.invoke, or to
// cons up a new object? Probably a new object.
// When it's know that theres just one target, this could cache the result of the
// traversal. Tho better to never assume that: could still cache all results... tho
// to be *fully* dynamic, would have to update cache every time AWT hierarchy changes,
// tho that's pretty much all the time with menu's and rollovers...
//AWTEventDispatch(this, ObjectInspectorPanel.class, "setTab", ObjectInspectorPanel.NOTES_TAB);
//new EventDispatch(this, ObjectInspectorPanel.class) {}
/*
new EventRaiser(this, ObjectInspectorPanel.class) {
public void dispatch(Object target) {
ObjectInspectorPanel oip = (ObjectInspectorPanel) target;
oip.setTab(ObjectInspectorPanel.NOTES_TAB);
//oip.setVisible(true); need to get parent window
// EventRaiser could cache last Window seen...
EventRaiser.stop();
}
}.raise();
*/
tufts.vue.gui.GUI.makeVisibleOnScreen(this, NotePanel.class);
//VUE.ObjectInspectorPanel.setTab(ObjectInspectorPanel.NOTES_TAB);
}
private JComponent ttNotes;
private String ttLastNotes;
public JComponent getToolTipComponent()
{
// todo: would be more efficent to listen for note change
// events instead of comparing the whole string every time
// -- especially for big notes (this goes for all the other
// LWIcon tool tips also)
if (ttNotes == null || !ttLastNotes.equals(mLWC.getNotes())) {
ttLastNotes = mLWC.getNotes();
int size = ttLastNotes.length();
//System.out.println("width="+width);
if (size > 50 || ttLastNotes.indexOf('\n') >= 0) {
JTextArea ta = new JTextArea(ttLastNotes, 1, 30);
//ta.setFont(FONT_SMALL);
ta.setFont(FONT_MEDIUM);
ta.setLineWrap(true);
ta.setWrapStyleWord(true);
ta.setSize(ta.getPreferredSize());
//System.out.println(" size="+ta.getSize());
//Dimension ps = ta.getPreferredSize();
//System.out.println("prefsize="+ps);
//System.out.println(" minsize="+ta.getMinimumSize());
ttNotes = ta;
} else {
ttNotes = new JLabel(ttLastNotes);
ttNotes.setFont(FONT_SMALL);
}
}
return ttNotes;
}
public void draw(DrawContext dc)
{
super.draw(dc);
double x = getX();
double y = getY();
dc.g.translate(x, y);
// an experiment in semantic zoom
/*
if (dc.zoom >= 8.0) {
dc.g.setFont(MinisculeFont);
dc.g.setColor(Color.gray);
dc.g.drawString(this.node.getNotes(), 0, (int)(super.height));
}*/
double x2 = (getWidth() - iconWidth) / 2;
double y2 = (getHeight() - iconHeight) / 2;
dc.g.translate(x2, y2);
x += x2;
y += y2;
dc.g.setColor(mColor);
dc.g.fill(pencil_body);
dc.g.setStroke(stroke);
dc.g.setColor(Color.white);
dc.g.fill(pencil_point);
dc.g.setColor(mColor);
dc.g.draw(pencil_point);
dc.g.fill(pencil_tip);
dc.g.translate(-x, -y);
}
}
static class Pathway extends LWIcon
{
private final static float MaxX = 224;
private final static float MaxY = 145;
private final static double scale = DefaultScale;
private final static double scaleInv = 1/scale;
private final static Stroke stroke = new BasicStroke((float)(0.5/scale));
static float iconWidth = (float) (MaxX * scale);
static float iconHeight = (float) (MaxY * scale);
//-------------------------------------------------------
private final static Line2D line1 = new Line2D.Float( 39,123, 92, 46);
private final static Line2D line2 = new Line2D.Float(101, 43, 153,114);
private final static Line2D line3 = new Line2D.Float(163,114, 224, 39);
private final static Ellipse2D dot1 = new Ellipse2D.Float( 0,95, 62,62);
private final static Ellipse2D dot2 = new Ellipse2D.Float( 65, 0, 62,62);
private final static Ellipse2D dot3 = new Ellipse2D.Float(127,90, 62,62);
Pathway(LWComponent lwc, Color c) { super(lwc, c); }
Pathway(LWComponent lwc) { super(lwc); }
boolean isWanted() {
return IconPref.getPathwayIconValue() && mLWC.inPathway();
}
void doDoubleClickAction() {
tufts.vue.gui.GUI.makeVisibleOnScreen(this, PathwayPanel.class);
//VUE.MapInspector.showTab("Pathway");
}
private JComponent ttPathway;
private String ttPathwayHtml;
public JComponent getToolTipComponent()
{
String html = "<html>";
Iterator i = mLWC.getPathways().iterator();
int n = 0;
while (i.hasNext()) {
//tufts.vue.Pathway p = (tufts.vue.Pathway) i.next();
LWPathway p = (LWPathway) i.next();
// todo perf: use StringBuffer
if (n++ > 0)
html += "<br>";
html += " In path: <b>" + p.getLabel() + "</b> ";
}
if (ttPathwayHtml == null || !ttPathwayHtml.equals(html)) {
ttPathway = new AALabel(html);
ttPathway.setFont(FONT_MEDIUM);
ttPathwayHtml = html;
}
return ttPathway;
}
void doSingleClickAction() {}
void draw(DrawContext dc)
{
super.draw(dc);
double x = getX();
double y = getY();
dc.g.translate(x, y);
double x2 = (getWidth() - iconWidth) / 2;
double y2 = (getHeight() - iconHeight) / 2;
dc.g.translate(x2, y2);
x += x2;
y += y2;
dc.g.scale(scale,scale);
dc.g.setColor(mColor);
dc.g.fill(dot1);
dc.g.fill(dot2);
dc.g.fill(dot3);
dc.g.setStroke(stroke);
dc.g.draw(line1);
dc.g.draw(line2);
dc.g.draw(line3);
dc.g.scale(scaleInv,scaleInv);
dc.g.translate(-x, -y);
}
}
static class MetaData extends LWIcon
{
//private final static int w = 28;
private final static int w = 16;
private final static float MaxX = 221;
private final static float MaxY = 114+w;
private final static double scale = DefaultScale;
private final static double scaleInv = 1/scale;
private final static AffineTransform t = AffineTransform.getScaleInstance(scale, scale);
private static float iconWidth = (float) (MaxX * scale);
private static float iconHeight = (float) (MaxY * scale);
//-------------------------------------------------------
private final static GeneralPath ul = new GeneralPath();
private final static GeneralPath ll = new GeneralPath();
private final static GeneralPath ur = new GeneralPath();
private final static GeneralPath lr = new GeneralPath();
static {
ul.moveTo(0,58);
ul.lineTo(96,0);
ul.lineTo(96,w);
ul.lineTo(0,58+w);
ul.closePath();
ul.transform(t);
ll.moveTo(0,58);
ll.lineTo(96,114);
ll.lineTo(96,114+w);
ll.lineTo(0,58+w);
ll.closePath();
ll.transform(t);
ur.moveTo(125,0);
ur.lineTo(221,58);
ur.lineTo(221,58+w);
ur.lineTo(125,w);
ur.closePath();
ur.transform(t);
lr.moveTo(221,58);
lr.lineTo(125,114);
lr.lineTo(125,114+w);
lr.lineTo(221,58+w);
lr.closePath();
lr.transform(t);
}
MetaData(LWComponent lwc, Color c) { super(lwc, c); }
MetaData(LWComponent lwc) { super(lwc); }
boolean isWanted() {
return
IconPref.getMetaDataIconValue() &&
mLWC.hasMetaData(edu.tufts.vue.metadata.VueMetadataElement.CATEGORY);
}
void doDoubleClickAction() {
System.out.println(this + " Meta-Data Action?");
}
void doSingleClickAction() {}
private JComponent ttMetaData;
private String ttMetaDataHtml;
public JComponent getToolTipComponent()
{
String html = "<html>";
html += mLWC.getMetaDataAsHTML();
if (ttMetaDataHtml == null || !ttMetaDataHtml.equals(html)) {
ttMetaData = new AALabel(html);
ttMetaData.setFont(FONT_MEDIUM);
ttMetaDataHtml = html;
}
return ttMetaData;
}
void draw(DrawContext dc)
{
super.draw(dc);
double x = getX() + (getWidth() - iconWidth) / 2;
double y = getY() + (getHeight() - iconHeight) / 2;
dc.g.translate(x, y);
dc.g.setColor(mColor);
dc.g.fill(ul);
dc.g.fill(ur);
dc.g.fill(ll);
dc.g.fill(lr);
dc.g.translate(-x, -y);
}
}
static class MergeSourceMetaData extends LWIcon
{
//private final static int w = 28;
private final static int w = 16;
private final static float MaxX = 221;
private final static float MaxY = 114+w;
private final static double scale = DefaultScale;
private final static double scaleInv = 1/scale;
private final static AffineTransform t = AffineTransform.getScaleInstance(scale, scale);
private static float iconWidth = (float) (MaxX * scale);
private static float iconHeight = (float) (MaxY * scale);
//-------------------------------------------------------
//private final static GeneralPath oval = new GeneralPath(new Ellipse2D.Float(0,0,3,3));
private final static Ellipse2D oval = new Ellipse2D.Float(0,0,3,3);
MergeSourceMetaData(LWComponent lwc, Color c) { super(lwc, c); }
MergeSourceMetaData(LWComponent lwc) { super(lwc); }
boolean isWanted() {
return
IconPref.getMetaDataIconValue() &&
mLWC.hasMetaData(edu.tufts.vue.metadata.VueMetadataElement.OTHER);
}
void doDoubleClickAction() {
System.out.println(this + " Merge Source Meta-Data Action?");
}
void doSingleClickAction() {}
private JComponent ttMetaData;
private String ttMetaDataHtml;
public JComponent getToolTipComponent()
{
String html = "<html>";
html += mLWC.getMetaDataAsHTML(edu.tufts.vue.metadata.VueMetadataElement.OTHER);
if (ttMetaDataHtml == null || !ttMetaDataHtml.equals(html)) {
ttMetaData = new AALabel(html);
ttMetaData.setFont(FONT_MEDIUM);
ttMetaDataHtml = html;
}
return ttMetaData;
}
void draw(DrawContext dc)
{
super.draw(dc);
double x = getX() + (getWidth() - iconWidth) / 2;
double y = getY() + (getHeight() - iconHeight) / 2;
dc.g.translate(x, y);
dc.g.setColor(mColor);
//dc.g.fill(oval);
dc.g.setStroke(STROKE_HALF);
dc.g.draw(oval);
//dc.g.fill(ul);
//dc.g.fill(ur);
//dc.g.fill(ll);
//dc.g.fill(lr);
dc.g.translate(-x, -y);
}
}
static class OntologicalMetaData extends LWIcon
{
//private final static int w = 28;
private final static int w = 16;
private final static float MaxX = 221;
private final static float MaxY = 114+w;
private final static double scale = DefaultScale;
private final static double scaleInv = 1/scale;
private final static AffineTransform t = AffineTransform.getScaleInstance(scale, scale);
private static float iconWidth = (float) (MaxX * scale);
private static float iconHeight = (float) (MaxY * scale);
//-------------------------------------------------------
private final static GeneralPath one = new GeneralPath();
private final static GeneralPath two = new GeneralPath();
private final static GeneralPath three = new GeneralPath();
private final static GeneralPath four = new GeneralPath();
private final static GeneralPath five = new GeneralPath();
static {
one.moveTo(73,96);
one.lineTo(0,81);
one.lineTo(11,46);
one.lineTo(79,77);
one.lineTo(74,84);
one.closePath();
one.transform(t);
two.moveTo(83,73);
two.lineTo(76,0);
two.lineTo(111,0);
two.lineTo(104,75);
two.lineTo(94,72);
two.closePath();
two.transform(t);
three.moveTo(107,77);
three.lineTo(176,47);
three.lineTo(186,80);
three.lineTo(113,96);
three.lineTo(112,86);
three.closePath();
three.transform(t);
four.moveTo(113,100);
four.lineTo(162,156);
four.lineTo(133,177);
four.lineTo(95,112);
four.lineTo(106,109);
four.closePath();
four.transform(t);
five.moveTo(91,112);
five.lineTo(53,177);
five.lineTo(24,156);
five.lineTo(74,100);
five.lineTo(81,109);
five.closePath();
five.transform(t);
}
OntologicalMetaData(LWComponent lwc, Color c) { super(lwc, c); }
OntologicalMetaData(LWComponent lwc) { super(lwc); }
boolean isWanted() {
return
IconPref.getMetaDataIconValue() &&
mLWC.hasMetaData(edu.tufts.vue.metadata.VueMetadataElement.ONTO_TYPE);
}
void doDoubleClickAction() {
System.out.println(this + " Ontological Meta-Data Action?");
}
void doSingleClickAction() {}
private JComponent ttMetaData;
private String ttMetaDataHtml;
public JComponent getToolTipComponent()
{
String html = "<html>";
html += mLWC.getMetaDataAsHTML(edu.tufts.vue.metadata.VueMetadataElement.ONTO_TYPE);
if (ttMetaDataHtml == null || !ttMetaDataHtml.equals(html)) {
ttMetaData = new AALabel(html);
ttMetaData.setFont(FONT_MEDIUM);
ttMetaDataHtml = html;
}
return ttMetaData;
}
void draw(DrawContext dc)
{
super.draw(dc);
double x = getX() + (getWidth() - iconWidth) / 2;
double y = getY() + (getHeight() - iconHeight) / 2;
dc.g.translate(x, y);
dc.g.setColor(mColor);
dc.g.fill(one);
dc.g.fill(two);
dc.g.fill(three);
dc.g.fill(four);
dc.g.fill(five);
dc.g.translate(-x, -y);
}
}
/* static class Behavior extends LWIcon
{
private final static float sMaxX = 194;
private final static float sMaxY = 155;
private final static double scale = DefaultScale;
private final static double scaleInv = 1/scale;
static float iconWidth = (float) (sMaxX * scale);
static float iconHeight = (float) (sMaxY * scale);
//-------------------------------------------------------
private final static int pw = 15; // plus-sign "stroke" width
private final static int bw = 10; // bracket "stroke" width
private final static int sl = 52; // bracket stub length
private final static Rectangle2D plus_vert = new Rectangle2D.Float(89,16, pw,123);
private final static Rectangle2D plus_horz = new Rectangle2D.Float(37,70, 123,pw);
private final static Rectangle2D bracket_left = new Rectangle2D.Float(0,bw-1, bw,sMaxY-bw*2+2);
private final static Rectangle2D bracket_right= new Rectangle2D.Float(sMaxX-bw,bw-1, bw,sMaxY-bw*2+2);
private final static Rectangle2D bracket_ul = new Rectangle2D.Float(0,0, sl,bw);
private final static Rectangle2D bracket_ur = new Rectangle2D.Float(sMaxX-sl,0, sl,bw);
private final static Rectangle2D bracket_ll = new Rectangle2D.Float(0,sMaxY-bw, sl,bw);
private final static Rectangle2D bracket_lr = new Rectangle2D.Float(sMaxX-sl,sMaxY-bw, sl,bw);
Behavior(LWComponent lwc, Color c) { super(lwc, c); }
Behavior(LWComponent lwc) { super(lwc); }
boolean isShowing() {
if (IconPref.getBehaviorIconValue())
return mLWC.hasResource() && mLWC.getResource() instanceof AssetResource;
else
return false;
}
void doDoubleClickAction() {
//VUE.ObjectInspectorPanel.setTab(ObjectInspectorPanel.INFO_TAB);
}
private JComponent ttBehavior;
private String ttBehaviorHtml;
public JComponent getToolTipComponent()
{
//String html = "<html>Behavior info: " + mLWC.getResource().getToolTipInformation();
final String html = "Right click on node to see disseminations.";
if (ttBehaviorHtml == null || !ttBehaviorHtml.equals(html)) {
ttBehavior = new AALabel(html);
ttBehavior.setFont(FONT_SMALL);
ttBehaviorHtml = html;
}
return ttBehavior;
}
void draw(DrawContext dc)
{
super.draw(dc);
double x = getX() + (getWidth() - iconWidth) / 2;
double y = getY() + (getHeight() - iconHeight) / 2;
dc.g.translate(x, y);
dc.g.scale(scale,scale);
dc.g.setColor(mColor);
dc.g.fill(plus_vert);
dc.g.fill(plus_horz);
dc.g.fill(bracket_left);
dc.g.fill(bracket_right);
dc.g.fill(bracket_ul);
dc.g.fill(bracket_ll);
dc.g.fill(bracket_ur);
dc.g.fill(bracket_lr);
dc.g.scale(scaleInv,scaleInv);
dc.g.translate(-x, -y);
}
}*/
static class Hierarchy extends LWIcon
{
private final static float MaxX = 220;
private final static float MaxY = 155;
private final static double scale = DefaultScale;
private final static double scaleInv = 1/scale;
private final static Stroke stroke = STROKE_TWO;
static float iconWidth = (float) (MaxX * scale);
static float iconHeight = (float) (MaxY * scale);
//-------------------------------------------------------
private final static Line2D line1 = new Line2D.Float(101, 16, 141, 16); // top horiz
private final static Line2D line2 = new Line2D.Float( 70, 76, 141, 76); // middle long horiz
private final static Line2D line3 = new Line2D.Float(101,136, 141,136); // bottom horiz
private final static Line2D line4 = new Line2D.Float(101, 16, 101,136); // vertical
private final static Rectangle2D box = new Rectangle2D.Float(0,51, 56,56);
private final static Rectangle2D rect1 = new Rectangle2D.Float(150, 0, 70,33);
private final static Rectangle2D rect2 = new Rectangle2D.Float(150, 63, 70,33);
private final static Rectangle2D rect3 = new Rectangle2D.Float(150,122, 70,33);
Hierarchy(LWComponent lwc, Color c) { super(lwc, c); }
Hierarchy(LWComponent lwc) { super(lwc); }
boolean isWanted() {
// TODO performance: getting complicated: compute in layout (and check for all text nodes, not just first)
// Will need to make sure layout() is called when removing items from nodes: only appears to be called upon adding
if (IconPref.getHierarchyIconValue()) {
if (mLWC.numChildren() == 1) {
LWComponent child0 = mLWC.getChild(0);
if (child0.isTextNode() || LWNode.isImageNode(mLWC))
return false;
else
return true;
} else if (mLWC.hasChildren())
return true;
}
return false;
}
// @Override
// void layout() {
// Log.debug("HIERARCY LAYOUT IN " + mLWC);
// }
void doDoubleClickAction() {
//VUE.ObjectInspectorPanel.setTab(ObjectInspectorPanel.TREE_TAB);
}
void doSingleClickAction() {}
private JLabel ttTree;
private String ttTreeHtml;
public JComponent getToolTipComponent()
{
if ((mLWC instanceof LWContainer) == false)
return new JLabel(VueResources.getString("jlabel.nochild"));
// todo perf: use StringBuffer
String html = "<html>" + getChildHtml(mLWC, 1);
if (html.endsWith("<br>"))
html = html.substring(0, html.length()-4);
//System.out.println("HTML [" + html + "]");
if (ttTreeHtml == null || !ttTreeHtml.equals(html)) {
ttTree = new AALabel(html);
ttTree.setFont(FONT_MEDIUM);
ttTreeHtml = html;
}
return ttTree;
}
private static final String Indent = " ";
private static final String RightMargin = Indent;
private String getChildHtml(LWComponent c, int indent)
{
// todo perf: use StringBuffer
String label = null;
if (indent == 1)
label = " <b>" + c.getDisplayLabel() + "</b>";
else
label = c.getDisplayLabel();
String html = label + RightMargin + "<br>";
if (!(c instanceof LWContainer))
return html;
//int n = 0;
for (LWComponent child : c.getChildren()) {
if (child.isTextNode())
continue;
//if (n++ > 0) html += "<br>";
for (int x = 0; x < indent; x++)
html += Indent;
if (indent % 2 == 0)
html += "- ";
else
html += "+ ";
html += getChildHtml(child, indent + 1);
}
return html;
}
void draw(DrawContext dc)
{
super.draw(dc);
double x = getX() + (getWidth() - iconWidth) / 2;
double y = getY() + (getHeight() - iconHeight) / 2;
dc.g.translate(x, y);
dc.g.scale(scale,scale);
dc.g.setColor(mColor);
dc.g.fill(box);
dc.g.fill(rect1);
dc.g.fill(rect2);
dc.g.fill(rect3);
dc.g.setStroke(stroke);
dc.g.draw(line1);
dc.g.draw(line2);
dc.g.draw(line3);
dc.g.draw(line4);
dc.g.scale(scaleInv,scaleInv);
dc.g.translate(-x, -y);
}
}
private static Font MinisculeFont = new Font("SansSerif", Font.PLAIN, 1);
//private static Font MinisculeFont = new Font("Arial Narrow", Font.PLAIN, 1);
}