package org.basex.gui.view.tree;
import org.basex.core.Context;
import static org.basex.core.Text.NO_SPACE;
import org.basex.data.Data;
import org.basex.data.Nodes;
import org.basex.gui.GUIConstants;
import static org.basex.gui.GUIConstants.*;
import org.basex.gui.GUIProp;
import org.basex.gui.layout.BaseXLayout;
import org.basex.gui.layout.BaseXPopup;
import org.basex.gui.view.View;
import org.basex.gui.view.ViewNotifier;
import org.basex.gui.view.ViewRect;
import org.basex.util.Token;
import org.basex.util.list.IntList;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.ListIterator;
/**
* This class offers a real tree view.
*
* @author BaseX Team 2005-12, BSD License
* @author Wolfgang Miller
*/
public final class TreeView extends View implements TreeConstants {
/** TreeBorders Object, contains cached pre values and borders. */
private TreeSubtree sub;
/** TreeRects Object, contains cached rectangles. */
private TreeRects tr;
/** Current font height. */
private int fontHeight;
/** Current mouse position x. */
private int mousePosX = -1;
/** Current mouse position y. */
private int mousePosY = -1;
/** Window width. */
private int wwidth = -1;
/** Window height. */
private int wheight = -1;
/** Window start. */
private int wstart;
/** Current Image of visualization. */
private BufferedImage treeImage;
/** Notified focus flag. */
private boolean refreshedFocus;
/** Distance between the levels. */
private int levelDistance;
/** Image of the current marked nodes. */
private BufferedImage markedImage;
/** If something is selected. */
private boolean selection;
/** The selection rectangle. */
private ViewRect selectRect;
/** The node height. */
private int nodeHeight;
/** Top margin. */
private int topMargin;
/** Distance between multiple trees. */
private double treedist;
/** Currently focused rectangle. */
private TreeRect frect;
/** Level of currently focused rectangle. */
private int flv = -1;
/** Focused root number. */
private int frn;
/** Focused pre. */
private int fpre;
/** New tree initialization. */
private Refresh paintType = Refresh.INIT;
/** Array with current root nodes. */
private int[] roots;
/** Not enough space. */
private boolean nes;
/** If something is in focus. */
private boolean inFocus;
/** Show attributes. */
private boolean showAtts;
/** Slim rectangles to text length. */
private boolean slimToText;
/**
* Default constructor.
* @param man view manager
*/
public TreeView(final ViewNotifier man) {
super(TREEVIEW, man);
new BaseXPopup(this, POPUP);
}
@Override
public void refreshContext(final boolean more, final boolean quick) {
paintType = sub == null ? Refresh.INIT : Refresh.CONTEXT;
repaint();
}
@Override
public void refreshFocus() {
refreshedFocus = true;
repaint();
}
@Override
public void refreshInit() {
if(!visible()) return;
paintType = Refresh.INIT;
repaint();
}
@Override
public void refreshLayout() {
paintType = Refresh.RESIZE;
repaint();
}
@Override
public void refreshMark() {
if(!nes) {
markNodes();
repaint();
}
}
@Override
public void refreshUpdate() {
paintType = Refresh.INIT;
repaint();
}
@Override
public boolean visible() {
final boolean v = gui.gprop.is(GUIProp.SHOWTREE);
if(!v) {
sub = null;
tr = null;
paintType = Refresh.INIT;
}
return v;
}
@Override
public void visible(final boolean v) {
gui.gprop.set(GUIProp.SHOWTREE, v);
}
@Override
protected boolean db() {
return true;
}
@Override
public void paintComponent(final Graphics g) {
final Context c = gui.context;
final Data data = c.data();
if(data == null) return;
if(showAttsChanged()) paintType = Refresh.INIT;
if(slimToTextChanged() && paintType == Refresh.VOID)
paintType = Refresh.RESIZE;
super.paintComponent(g);
gui.painting = true;
roots = gui.context.current().list;
if(roots.length == 0) return;
for(int i = 0; !showAtts && i < roots.length; ++i) {
if(data.kind(roots[i]) == Data.ATTR) {
drawMessage(g, NO_ATTS);
return;
}
}
smooth(g);
g.setFont(font);
fontHeight = g.getFontMetrics().getHeight();
if(paintType == Refresh.INIT) {
sub = new TreeSubtree(data, showAtts);
tr = new TreeRects();
}
if(paintType == Refresh.INIT || paintType == Refresh.CONTEXT)
sub.generateBorders(c);
/* If window-size changed. */
final boolean winChange;
if((winChange = windowSizeChanged()) && paintType == Refresh.VOID
|| paintType == Refresh.INIT || paintType == Refresh.CONTEXT
|| paintType == Refresh.RESIZE) {
treedist = tr.generateRects(sub, g, c, wstart, wwidth, slimToText);
nes = treedist == -1;
if(!nes) {
markedImage = null;
setLevelDistance();
createNewMainImage();
if(gui.context.marked.size() > 0) markNodes();
}
}
if(nes) {
drawMessage(g, NOT_ENOUGH_SPACE);
return;
}
g.drawImage(treeImage, 0, 0, wwidth, wheight, this);
if(selection) {
if(selectRect != null) {
// draw selection
final int x = selectRect.w < 0 ? selectRect.x + selectRect.w
: selectRect.x;
final int y = selectRect.h < 0 ? selectRect.y + selectRect.h
: selectRect.y;
final int w = Math.abs(selectRect.w);
final int h = Math.abs(selectRect.h);
g.setColor(colormark1);
g.drawRect(x, y, w, h);
}
markNodes();
}
if(markedImage != null) g.drawImage(markedImage, 0, 0, wwidth, wheight,
this);
// highlights the focused node
inFocus = paintType == Refresh.VOID && focus();
if(inFocus && !winChange) {
if(!refreshedFocus && tr.bigRect(sub, frn, flv)) {
final int f = getMostSizedNode(data, frn, flv, frect, fpre);
if(f >= 0) fpre = f;
}
highlightNode(g, frn, flv, frect, fpre, -1, Draw.HIGHLIGHT);
refreshedFocus = false;
}
paintType = Refresh.VOID;
gui.painting = false;
}
/**
* Creates new image and draws rectangles in it.
*/
private void createNewMainImage() {
treeImage = createImage();
final Graphics tg = treeImage.getGraphics();
final int rl = roots.length;
tg.setFont(font);
smooth(tg);
for(int rn = 0; rn < rl; ++rn) {
final int h = sub.getSubtreeHeight(rn);
for(int lv = 0; lv < h; ++lv) {
final boolean br = tr.bigRect(sub, rn, lv);
final TreeRect[] lr = tr.getTreeRectsPerLevel(rn, lv);
for(int i = 0; i < lr.length; ++i) {
final TreeRect r = lr[i];
final int pre = sub.getPrePerIndex(rn, lv, i);
drawRectangle(tg, rn, lv, r, pre, Draw.RECTANGLE);
}
if(br) {
final TreeRect r = lr[0];
final int ww = r.x + r.w - 1;
final int x = r.x + 1;
drawBigRectSquares(tg, lv, x, ww, 4);
}
}
if(SHOW_CONN_MI) {
final TreeRect rr = tr.getTreeRectPerIndex(rn, 0, 0);
highlightDescendants(tg, rn, 0, rr, roots[rn], getRectCenter(rr),
Draw.CONNECTION);
}
}
}
/**
* Displays Message if there is not enough space to draw all roots or an
* attribute-context but no attributes in cache.
* @param g graphics reference
* @param t type
*/
private void drawMessage(final Graphics g, final byte t) {
final int mw = wwidth >> 1;
final int mh = wheight >> 1;
String message = "";
switch(t){
case NOT_ENOUGH_SPACE: message = NO_SPACE;
break;
case NO_ATTS: message = "Enable attributes in Tree Options.";
break;
}
final int x = mw - (BaseXLayout.width(g, message) >> 1);
final int y = mh + fontHeight;
g.setColor(Color.BLACK);
g.drawString(message, x, y);
}
/**
* Draws the squares inside big rectangles.
* @param g graphics reference
* @param lv level
* @param x x-coordinate
* @param w width
* @param ss square-size
*/
private void drawBigRectSquares(final Graphics g, final int lv, final int x,
final int w, final int ss) {
int xx = x;
final int y = getYperLevel(lv);
int nh = nodeHeight;
g.setColor(color2A);
while(nh > 0) {
nh -= ss;
if(nh < 0) nh = 0;
g.drawLine(xx, y + nh, w, y + nh);
}
while(xx < w) {
xx = xx + ss - 1 < w ? xx + ss : xx + ss - 1;
g.drawLine(xx, y, xx, y + nodeHeight);
}
}
/**
* Return boolean if position is marked or not.
* @param x x-coordinate
* @param y y-coordinate
* @return position is marked or not
*/
private boolean marked(final int x, final int y) {
if(markedImage != null) {
final int h = markedImage.getHeight();
final int w = markedImage.getWidth();
if(y >= h || 0 > y || x >= w || 0 > x) return false;
final Color markc = new Color(markedImage.getRGB(x, y));
return markc.getRed() > 0 && markc.getBlue() == 0 && markc.getGreen()
== 0;
}
return false;
}
/**
* Draws Rectangles.
* @param g graphics reference
* @param rn root
* @param lv level
* @param r rectangle
* @param pre pre
* @param t draw type
*/
private void drawRectangle(final Graphics g, final int rn, final int lv,
final TreeRect r, final int pre, final Draw t) {
final int y = getYperLevel(lv);
final int h = nodeHeight;
final boolean br = tr.bigRect(sub, rn, lv);
boolean txt = !br && fontHeight <= h;
final boolean fill;
boolean border = false;
final int xx = r.x;
final int ww = r.w;
final boolean marked = marked(xx, y);
Color borderColor = null;
Color fillColor;
Color textColor = Color.BLACK;
switch(t) {
case RECTANGLE:
borderColor = getColorPerLevel(lv, false);
fillColor = getColorPerLevel(lv, true);
txt &= DRAW_NODE_TEXT;
border = BORDER_RECTANGLES;
fill = FILL_RECTANGLES;
break;
case HIGHLIGHT:
borderColor = color4;
final int alpha = 0xDD000000;
final int rgb = GUIConstants.LGRAY.getRGB();
fillColor = new Color(rgb + alpha, true);
if(h > 4) border = true;
fill = !br && !marked;
break;
case MARK:
borderColor = h > 2 && r.w > 4 ? colormark1A : colormark1;
fillColor = colormark1;
border = true;
fill = true;
break;
case DESCENDANTS:
final int alphaD = 0xDD000000;
final int rgbD = color(6).getRGB();
fillColor = new Color(rgbD + alphaD, true);
borderColor = color(8);
textColor = Color.WHITE;
fill = !marked;
border = true;
if(h < 4) {
fillColor = SMALL_SPACE_COLOR;
borderColor = fillColor;
txt = false;
}
break;
case PARENT:
default:
fillColor = color(6);
textColor = Color.WHITE;
fill = !br && !marked;
border = !br;
if(h < 4) {
fillColor = SMALL_SPACE_COLOR;
borderColor = color(8);
txt = false;
}
break;
}
if(border) {
g.setColor(borderColor);
g.drawRect(xx, y, ww, h);
}
if(fill) {
g.setColor(fillColor);
g.fillRect(xx + 1, y + 1, ww - 1, h - 1);
}
if(txt && (fill || !FILL_RECTANGLES)) {
g.setColor(textColor);
drawRectangleText(g, rn, lv, r, pre);
}
}
/**
* Draws text into rectangle.
* @param g graphics reference
* @param rn root
* @param lv level
* @param r rectangle
* @param pre pre
*/
private void drawRectangleText(final Graphics g, final int rn, final int lv,
final TreeRect r, final int pre) {
String s = Token.string(TreeRects.getText(gui.context, rn, pre)).trim();
if(r.w < BaseXLayout.width(g, s) && r.w < BaseXLayout.width(
g, ".." + s.substring(s.length() - 1)) + MIN_TXT_SPACE) return;
final int x = r.x;
final int y = getYperLevel(lv);
final int rm = x + (int) (r.w / 2f);
int tw = BaseXLayout.width(g, s);
if(tw > r.w) {
s += "..";
while((tw = BaseXLayout.width(g, s)) + MIN_TXT_SPACE > r.w
&& s.length() > 3) {
s = s.substring(0, (s.length() - 2) / 2) + "..";
}
}
final int yy = (int) (y + (nodeHeight + fontHeight - 4) / 2d);
g.drawString(s, (int) (rm - tw / 2d + BORDER_PADDING), yy);
}
/**
* Returns draw Color.
* @param l the current level
* @param fill if true it returns fill color, rectangle color else
* @return draw color
*/
private static Color getColorPerLevel(final int l, final boolean fill) {
final int till = l < CHANGE_COLOR_TILL ? l : CHANGE_COLOR_TILL;
return fill ? color(till) : color(till + 2);
}
/**
* Marks nodes inside the dragged selection.
*/
private void markSelectedNodes() {
final int x = selectRect.w < 0 ? selectRect.x + selectRect.w : selectRect.x;
final int y = selectRect.h < 0 ? selectRect.y + selectRect.h : selectRect.y;
final int w = Math.abs(selectRect.w);
final int h = Math.abs(selectRect.h);
final int t = y + h;
final int size = sub.getMaxSubtreeHeight();
final IntList list = new IntList();
final int rl = roots.length;
final int rs = getTreePerX(x);
final int re = getTreePerX(x + w);
for(int r = rs < 0 ? 0 : rs; r <= re; ++r) {
for(int i = 0; i < size; ++i) {
final int yL = getYperLevel(i);
if(i < sub.getSubtreeHeight(r) && (yL >= y || yL + nodeHeight >= y)
&& (yL <= t || yL + nodeHeight <= t)) {
final TreeRect[] rlv = tr.getTreeRectsPerLevel(r, i);
final int s = sub.levelSize(r, i);
if(tr.bigRect(sub, r, i)) {
if(rl > 1) {
final TreeBorder tb = sub.getTreeBorder(r, i);
final int si = tb.size;
for(int n = 0; n < si; ++n) {
list.add(sub.getPrePerIndex(r, i, n));
}
} else {
final int mw = rlv[0].w;
int sPrePos = (int) (s * (x - wstart) / (double) mw);
int ePrePos = (int) (s * (x - wstart + w) / (double) mw);
if(sPrePos < 0) sPrePos = 0;
if(ePrePos >= s) ePrePos = s - 1;
do {
list.add(sub.getPrePerIndex(r, i, sPrePos));
} while(sPrePos++ < ePrePos);
}
} else {
for(int j = 0; j < s; ++j) {
final TreeRect rect = rlv[j];
if(rect.contains(x, w)) list.add(sub.getPrePerIndex(r, i, j));
}
}
}
}
}
gui.notify.mark(new Nodes(list.toArray(), gui.context.data()), this);
}
/**
* Creates a new translucent BufferedImage.
* @return new translucent BufferedImage
*/
private BufferedImage createImage() {
return new BufferedImage(Math.max(1, wwidth), Math.max(1, wheight),
Transparency.TRANSLUCENT);
}
/**
* Highlights the marked nodes.
*/
private void markNodes() {
markedImage = createImage();
final Graphics mg = markedImage.getGraphics();
smooth(mg);
mg.setFont(font);
final int[] mark = gui.context.marked.list;
if(mark.length == 0) return;
int rn = 0;
while(rn < roots.length) {
final LinkedList<Integer> marklink = new LinkedList<Integer>();
for(int i = 0; i < mark.length; ++i)
marklink.add(i, mark[i]);
for(int lv = 0; lv < sub.getSubtreeHeight(rn); ++lv) {
final int y = getYperLevel(lv);
final ListIterator<Integer> li = marklink.listIterator();
if(tr.bigRect(sub, rn, lv)) {
while(li.hasNext()) {
final int pre = li.next();
final TreeRect rect = tr.searchRect(sub, rn, lv, pre);
final int ix = sub.getPreIndex(rn, lv, pre);
if(ix > -1) {
li.remove();
final int x = (int) (rect.w * ix / (double) sub.levelSize(rn,
lv));
mg.setColor(colormark1);
mg.fillRect(rect.x + x, y, 2, nodeHeight + 1);
}
}
} else {
while(li.hasNext()) {
final int pre = li.next();
final TreeRect rect = tr.searchRect(sub, rn, lv, pre);
if(rect != null) {
li.remove();
drawRectangle(mg, rn, lv, rect, pre, Draw.MARK);
}
}
}
}
++rn;
}
}
/**
* Returns position inside big rectangle.
* @param rn root
* @param lv level
* @param pre pre
* @param r rectangle
* @return position
*/
private int getBigRectPosition(final int rn, final int lv, final int pre,
final TreeRect r) {
final int idx = sub.getPreIndex(rn, lv, pre);
final double ratio = idx / (double) sub.levelSize(rn, lv);
return r.x + (int) Math.round(r.w * ratio) + 1;
}
/**
* Draws node inside big rectangle.
* @param g the graphics reference
* @param rn root
* @param lv level
* @param r rectangle
* @param pre pre
* @return node center
*/
private int drawNodeInBigRectangle(final Graphics g, final int rn,
final int lv, final TreeRect r, final int pre) {
final int y = getYperLevel(lv);
final int np = getBigRectPosition(rn, lv, pre, r);
g.setColor(nodeHeight < 4 ? SMALL_SPACE_COLOR : color(7));
g.drawLine(np, y, np, y + nodeHeight);
return np;
}
/**
* Draws parent connection.
* @param g the graphics reference
* @param lv level
* @param r rectangle
* @param px parent x
* @param brx bigrect x
*/
private void drawParentConnection(final Graphics g, final int lv,
final TreeRect r, final int px, final int brx) {
final int y = getYperLevel(lv);
g.setColor(color(7));
g.drawLine(px, getYperLevel(lv + 1) - 1, brx == -1 ? (2 * r.x + r.w) / 2
: brx, y + nodeHeight + 1);
}
/**
* Highlights nodes.
* @param g the graphics reference
* @param rn root
* @param pre pre
* @param r rectangle to highlight
* @param lv level
* @param px parent's x value
* @param t highlight type
*/
private void highlightNode(final Graphics g, final int rn, final int lv,
final TreeRect r, final int pre, final int px, final Draw t) {
if(lv == -1) return;
final boolean br = tr.bigRect(sub, rn, lv);
final boolean root = roots[rn] == pre;
final int height = sub.getSubtreeHeight(rn);
final Data d = gui.context.data();
final int k = d.kind(pre);
final int size = d.size(pre, k);
// rectangle center
final int rc;
if(br) {
rc = drawNodeInBigRectangle(g, rn, lv, r, pre);
} else {
drawRectangle(g, rn, lv, r, pre, t);
rc = getRectCenter(r);
}
// draw parent node connection
if(px > -1 && MIN_NODE_DIST_CONN <= levelDistance) drawParentConnection(g,
lv, r, px, rc);
// if there are ancestors draw them
if(!root) {
final int par = d.parent(pre, k);
final int lvp = lv - 1;
final TreeRect parRect = tr.searchRect(sub, rn, lvp, par);
if(parRect == null) return;
highlightNode(g, rn, lvp, parRect, par, rc, Draw.PARENT);
}
// if there are descendants draw them
if((t == Draw.CONNECTION || t == Draw.HIGHLIGHT) && size > 1 &&
lv + 1 < height) highlightDescendants(g, rn, lv, r, pre, rc, t);
// draws node text
if(t == Draw.HIGHLIGHT) drawThumbnails(g, rn, lv, pre, r, root);
}
/**
* Draws thumbnails.
* @param g the graphics reference
* @param rn root
* @param lv level
* @param pre pre
* @param r rect
* @param root root flag
*/
private void drawThumbnails(final Graphics g, final int rn, final int lv,
final int pre, final TreeRect r, final boolean root) {
final int x = r.x;
final int y = getYperLevel(lv);
final int h = nodeHeight;
final String s = Token.string(TreeRects.getText(gui.context, rn, pre));
final int w = BaseXLayout.width(g, s);
g.setColor(color(8));
if(root) {
g.fillRect(x, y + h, w + 2, fontHeight + 2);
g.setColor(color(6));
g.drawRect(x - 1, y + h + 1, w + 3, fontHeight + 1);
g.setColor(Color.WHITE);
g.drawString(s, r.x + 1, (int) (y + h + (float) fontHeight) - 2);
} else {
g.fillRect(r.x, y - fontHeight, w + 2, fontHeight);
g.setColor(color(6));
g.drawRect(r.x - 1, y - fontHeight - 1, w + 3, fontHeight + 1);
g.setColor(Color.WHITE);
g.drawString(s, r.x + 1, (int) (y - h / (float) fontHeight) - 2);
}
}
/**
* Highlights descendants.
* @param g the graphics reference
* @param rn root
* @param lv level
* @param r rectangle to highlight
* @param pre pre
* @param px parent's x value
* @param t draw type
*/
private void highlightDescendants(final Graphics g, final int rn,
final int lv, final TreeRect r, final int pre, final int px,
final Draw t) {
final Data d = gui.context.data();
final boolean br = tr.bigRect(sub, rn, lv);
if(!br && t != Draw.CONNECTION) drawRectangle(g, rn, lv, r, pre, t);
final int lvd = lv + 1;
final TreeBorder[] sbo = sub.subtree(d, pre);
if(sub.getSubtreeHeight(rn) >= lvd && sbo.length >= 2) {
final boolean brd = tr.bigRect(sub, rn, lvd);
if(brd) {
drawBigRectDescendants(g, rn, lvd, sbo, px, t);
} else {
final TreeBorder bo = sbo[1];
final TreeBorder bos = sub.getTreeBorder(rn, lvd);
final int start = bo.start >= bos.start ? bo.start - bos.start
: bo.start;
for(int j = 0; j < bo.size; ++j) {
final int dp = sub.getPrePerIndex(rn, lvd, j + start);
final TreeRect dr = tr.getTreeRectPerIndex(rn, lvd, j + start);
if(SHOW_DESCENDANTS_CONN && levelDistance >= MIN_NODE_DIST_CONN)
drawDescendantsConn(g, lvd, dr, px, t);
highlightDescendants(g, rn, lvd, dr, dp, getRectCenter(dr),
t == Draw.CONNECTION ? Draw.CONNECTION : Draw.DESCENDANTS);
}
}
}
}
/**
* Returns rectangle center.
* @param r TreeRect
* @return center
*/
private static int getRectCenter(final TreeRect r) {
return (2 * r.x + r.w) / 2;
}
/**
* Draws descendants for big rectangles.
* @param g graphics reference
* @param rn root
* @param lv level
* @param subt subtree
* @param parc parent center
* @param t draw type
*/
private void drawBigRectDescendants(final Graphics g, final int rn,
final int lv, final TreeBorder[] subt, final int parc, final Draw t) {
int lvv = lv;
int cen = parc;
int i;
for(i = 1; i < subt.length && tr.bigRect(sub, rn, lvv); ++i) {
final TreeBorder bos = sub.getTreeBorder(rn, lvv);
final TreeBorder bo = subt[i];
final TreeRect r = tr.getTreeRectPerIndex(rn, lvv, 0);
final int start = bo.start - bos.start;
final double sti = start / (double) bos.size;
final double eni = (start + bo.size) / (double) bos.size;
final int df = r.x + (int) (r.w * sti);
final int dt = r.x + (int) (r.w * eni);
final int ww = Math.max(dt - df, 2);
if(MIN_NODE_DIST_CONN <= levelDistance) drawDescendantsConn(g, lvv,
new TreeRect(df, ww), cen, t);
cen = (2 * df + ww) / 2;
switch(t) {
case CONNECTION:
break;
default:
final int rgb = color(7).getRGB();
final int alpha = 0x33000000;
g.setColor(nodeHeight < 4 ? SMALL_SPACE_COLOR : new Color(
rgb + alpha, false));
if(nodeHeight > 2) {
g.drawRect(df, getYperLevel(lvv) + 1, ww, nodeHeight - 2);
} else {
g.drawRect(df, getYperLevel(lvv), ww, nodeHeight);
}
}
if(lvv + 1 < sub.getSubtreeHeight(rn)
&& !tr.bigRect(sub, rn, lvv + 1)) {
final Data d = gui.context.data();
for(int j = start; j < start + bo.size; ++j) {
final int pre = sub.getPrePerIndex(rn, lvv, j);
final int pos = getBigRectPosition(rn, lvv, pre, r);
final int k = d.kind(pre);
final int s = d.size(pre, k);
if(s > 1) highlightDescendants(g, rn, lvv, r, pre, pos,
t == Draw.HIGHLIGHT || t == Draw.DESCENDANTS ?
Draw.DESCENDANTS : Draw.CONNECTION);
}
}
++lvv;
}
}
/**
* Returns connection color.
* @param t draw type
* @return color
*/
private static Color getConnectionColor(final Draw t) {
final int alpha;
final int rgb;
switch(t) {
case CONNECTION:
alpha = 0x20000000;
rgb = color(4).getRGB();
return new Color(rgb + alpha, true);
default:
alpha = 0x60000000;
rgb = color(8).getRGB();
return new Color(rgb + alpha, true);
}
}
/**
* Draws descendants connection.
* @param g graphics reference
* @param lv level
* @param r TreeRect
* @param parc parent center
* @param t draw type
*/
private void drawDescendantsConn(final Graphics g, final int lv,
final TreeRect r, final int parc, final Draw t) {
final int pary = getYperLevel(lv - 1) + nodeHeight;
final int prey = getYperLevel(lv) - 1;
final int boRight = r.x + r.w + BORDER_PADDING - 2;
final int boLeft = r.x + BORDER_PADDING;
final int boTop = prey + 1;
final Color c = getConnectionColor(t);
g.setColor(c);
if(boRight - boLeft > 2) {
g.fillPolygon(new int[] { parc, boRight, boLeft}, new int[] { pary,
boTop, boTop}, 3);
} else {
g.drawLine((boRight + boLeft) / 2, boTop, parc, pary);
}
}
/**
* Finds rectangle at cursor position.
* @return focused rectangle
*/
private boolean focus() {
if(refreshedFocus) {
fpre = gui.context.focused;
for(int r = 0; r < roots.length; ++r) {
for(int i = 0; i < sub.getSubtreeHeight(r); ++i) {
if(tr.bigRect(sub, r, i)) {
final int index = sub.getPreIndex(r, i, fpre);
if(index > -1) {
frn = r;
frect = tr.getTreeRectsPerLevel(r, i)[0];
flv = i;
return true;
}
} else {
final TreeRect rect = tr.searchRect(sub, r, i, fpre);
if(rect != null) {
frn = r;
frect = rect;
flv = i;
return true;
}
}
}
}
} else {
final int lv = getLevelPerY(mousePosY);
if(lv < 0) return false;
final int mx = mousePosX;
final int rn = frn = getTreePerX(mx);
final int h = sub.getSubtreeHeight(rn);
if(h < 0 || lv >= h) return false;
final TreeRect[] rL = tr.getTreeRectsPerLevel(rn, lv);
for(int i = 0; i < rL.length; ++i) {
final TreeRect r = rL[i];
if(r.contains(mx)) {
frect = r;
flv = lv;
final int pre;
// if multiple pre values, then approximate pre value
if(tr.bigRect(sub, rn, lv)) {
pre = tr.getPrePerXPos(sub, rn, lv, mx);
} else {
pre = sub.getPrePerIndex(rn, lv, i);
}
fpre = pre;
gui.notify.focus(pre, this);
refreshedFocus = false;
return true;
}
}
}
refreshedFocus = false;
return false;
}
/**
* Returns the y-axis value for a given level.
* @param level the level
* @return the y-axis value
*/
private int getYperLevel(final int level) {
return level * nodeHeight + level * levelDistance + topMargin;
}
/**
* Determines tree number.
* @param x x-axis value
* @return tree number
*/
private int getTreePerX(final int x) {
return (int) ((x - wstart) / treedist);
}
/**
* Determines the level of a y-axis value.
* @param y the y-axis value
* @return the level if inside a node rectangle, -1 else
*/
private int getLevelPerY(final int y) {
final double f = (y - topMargin) / ((float) levelDistance + nodeHeight);
final double b = nodeHeight / (float) (levelDistance + nodeHeight);
return f <= (int) f + b ? (int) f : -1;
}
/**
* Sets optimal distance between levels.
*/
private void setLevelDistance() {
final int h = wheight - BOTTOM_MARGIN;
int lvs = 0;
for(int i = 0; i < roots.length; ++i) {
final int th = sub.getSubtreeHeight(i);
if(th > lvs) lvs = th;
}
nodeHeight = MAX_NODE_HEIGHT;
int lD;
while((lD = (int) ((h - lvs * nodeHeight) / (double) (lvs - 1))) <
(nodeHeight <= BEST_NODE_HEIGHT ? MIN_LEVEL_DISTANCE
: BEST_LEVEL_DISTANCE)
&& nodeHeight >= MIN_NODE_HEIGHT)
nodeHeight--;
levelDistance = lD < MIN_LEVEL_DISTANCE ? MIN_LEVEL_DISTANCE
: lD > MAX_LEVEL_DISTANCE ? MAX_LEVEL_DISTANCE : lD;
final int ih = (int) ((h - (levelDistance * (lvs - 1) + lvs * nodeHeight))
/ 2d);
topMargin = ih < TOP_MARGIN ? TOP_MARGIN : ih;
}
/**
* Returns true if show attributes has changed.
* @return show attributes has changed
*/
private boolean showAttsChanged() {
final GUIProp gprop = gui.gprop;
if(gprop.is(GUIProp.TREEATTS) == showAtts) return false;
showAtts = !showAtts;
return true;
}
/**
* Returns true if slim to text has changed.
* @return slim to text has changed
*/
private boolean slimToTextChanged() {
final GUIProp gprop = gui.gprop;
if(gprop.is(GUIProp.TREESLIMS) == slimToText) return false;
slimToText = !slimToText;
return true;
}
/**
* Returns true if window-size has changed.
* @return window-size has changed
*/
private boolean windowSizeChanged() {
if(wwidth > -1 && wheight > -1 && getHeight() == wheight
&& getWidth() == wwidth + 2 * LEFT_AND_RIGHT_MARGIN) return false;
wheight = getHeight();
wstart = LEFT_AND_RIGHT_MARGIN;
wwidth = getWidth() - 2 * LEFT_AND_RIGHT_MARGIN;
return true;
}
/**
* Returns number of hit nodes.
* @param rn root
* @param lv level
* @param r rectangle
* @return size
*/
private int getHitBigRectNodesNum(final int rn, final int lv,
final TreeRect r) {
final int w = r.w;
final int ls = sub.levelSize(rn, lv);
return Math.max(ls / Math.max(w, 1), 1);
}
/**
* Returns most sized node.
* @param d the data reference
* @param rn root
* @param lv level
* @param r rectangle
* @param p pre
* @return deepest node pre
*/
private int getMostSizedNode(final Data d, final int rn, final int lv,
final TreeRect r, final int p) {
final int size = getHitBigRectNodesNum(rn, lv, r);
final int idx = sub.getPreIndex(rn, lv, p);
if(idx < 0) return -1;
int dpre = -1;
int si = 0;
for(int i = 0; i < size; ++i) {
final int pre = sub.getPrePerIndex(rn, lv, i + idx);
final int k = d.kind(pre);
final int s = d.size(pre, k);
if(s > si) {
si = s;
dpre = pre;
}
}
return dpre;
}
@Override
public void mouseMoved(final MouseEvent e) {
if(gui.updating) return;
super.mouseMoved(e);
// refresh mouse focus
mousePosX = e.getX();
mousePosY = e.getY();
repaint();
}
@Override
public void mousePressed(final MouseEvent e) {
final boolean left = SwingUtilities.isLeftMouseButton(e);
final boolean right = !left;
if(!inFocus || !right && !left || frect == null) return;
if(left) {
if(flv >= sub.getSubtreeHeight(frn)) return;
if(tr.bigRect(sub, frn, flv)) {
final Nodes ns = new Nodes(gui.context.data());
int sum = getHitBigRectNodesNum(frn, flv, frect);
final int fix = sub.getPreIndex(frn, flv, fpre);
if(fix + sum + 1 == sub.levelSize(frn, flv)) ++sum;
final int[] m = new int[sum];
for(int i = 0; i < sum; ++i) {
final int pre = sub.getPrePerIndex(frn, flv, i + fix);
if(pre == -1) break;
m[i] = pre;
}
ns.union(m);
gui.notify.mark(ns, null);
} else {
gui.notify.mark(0, null);
}
if(e.getClickCount() > 1) {
gui.notify.context(gui.context.marked, false, this);
refreshContext(false, false);
}
} else {
if(!marked(mousePosX, mousePosY)) {
gui.notify.mark(0, null);
}
}
}
@Override
public void mouseWheelMoved(final MouseWheelEvent e) {
if(gui.updating || gui.context.focused == -1) return;
if(e.getWheelRotation() <= 0) gui.notify.context(new Nodes(
gui.context.focused, gui.context.data()), false, null);
else gui.notify.hist(false);
}
@Override
public void mouseDragged(final MouseEvent e) {
if(gui.updating || e.isShiftDown()) return;
if(!selection) {
selection = true;
selectRect = new ViewRect();
selectRect.x = e.getX();
selectRect.y = e.getY();
selectRect.h = 1;
selectRect.w = 1;
} else {
final int x = e.getX();
final int y = e.getY();
selectRect.w = x - selectRect.x;
selectRect.h = y - selectRect.y;
}
markSelectedNodes();
repaint();
}
@Override
public void mouseReleased(final MouseEvent e) {
if(gui.updating || gui.painting) return;
selection = false;
repaint();
}
}