Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.basex.gui.view.map.MapRenderer Maven / Gradle / Ivy
package org.basex.gui.view.map;
import static org.basex.gui.GUIConstants.*;
import static org.basex.util.FTToken.*;
import static org.basex.util.Token.*;
import java.awt.*;
import java.util.*;
import org.basex.core.*;
import org.basex.gui.layout.*;
import org.basex.query.util.ft.*;
import org.basex.util.ft.*;
import org.basex.util.list.*;
/**
* This class assembles utility methods for painting rectangle contents.
*
* @author BaseX Team 2005-22, BSD License
* @author Christian Gruen
* @author Sebastian Gath
*/
final class MapRenderer {
/** Graphics reference. */
private final Graphics g;
/** Color for each tooltip token. */
private BoolList ttcol;
/** Index of tooltip token to underline. */
private int ul;
/**
* Private constructor.
* @param g graphics reference
*/
MapRenderer(final Graphics g) {
this.g = g;
}
/**
* Calculates the height of the specified text.
* @param rect rectangle
* @param text text to be drawn
* @return last height that was occupied
*/
int calcHeight(final MapRect rect, final byte[] text) {
return drawText(rect, text, false);
}
/**
* Draws a text.
* @param rect rectangle
* @param text text to be drawn
*/
void drawText(final MapRect rect, final byte[] text) {
drawText(rect, text, true);
}
/**
* Draws the specified string. Chops the last character if space is not enough space.
* @param text text to be drawn
* @param x x coordinate
* @param y y coordinate
* @param w width
*/
void chopText(final byte[] text, final int x, final int y, final int w) {
BaseXLayout.chopString(g, text, x, y, w, fontSize);
}
/**
* Draws a text.
* @param rect rectangle
* @param text text to be drawn
* @param draw draw text (otherwise: just calculate space)
* @return height of the text
*/
private int drawText(final MapRect rect, final byte[] text, final boolean draw) {
// limit string to given space
final int fh = (int) (1.2 * fontSize);
final Color textc = g.getColor();
int xx = rect.x;
int yy = rect.y + fh;
final int ww = rect.w;
// get index on first pre value
int ll = 0;
final FTLexer lexer = new FTLexer().original().init(text);
while(lexer.hasNext()) {
final FTSpan span = lexer.next();
byte[] token = span.text;
int wl = 0;
final int tl = token.length;
for(int t = 0; t < tl; t += cl(token, t)) {
wl += BaseXLayout.width(g, cp(token, t));
}
if(ll + wl >= ww) {
xx = rect.x;
if(ll != 0) yy += fh;
if(yy + fh > rect.y + rect.h) {
// text to high, skip drawing
if(draw) g.drawString(Text.DOTS, xx + ll, yy);
return rect.h;
}
ll = 0;
if(draw && wl >= ww) {
// single word is too long for the rectangle
int twl = 2 * BaseXLayout.width(g, '.');
if(twl >= ww) return Integer.MAX_VALUE;
int n = 0;
for(; n < tl; n += cl(token, n)) {
final int l = BaseXLayout.width(g, cp(token, n));
if(twl + l >= ww) break;
twl += l;
}
token = Arrays.copyOf(token, n + 2);
token[n] = '.';
token[n + 1] = '.';
}
}
if(draw) {
// color each full-text hit
g.setColor(rect.pos != null && rect.pos.contains(span.pos) && !span.del ? GREEN : textc);
g.drawString(string(token), xx + ll, yy);
}
ll += wl;
if(lexer.paragraph()) {
// new paragraph
ll = 0;
yy += fh;
if(yy + fh > rect.y + rect.h) {
// text to high, skip drawing
if(draw) g.drawString(Text.DOTS, xx + ll, yy);
return rect.h;
}
}
}
return yy - rect.y;
}
/**
* Draws a text using thumbnail visualization.
* Calculates the needed space and chooses an abstraction level.
* Token/Sentence/Paragraphs
* @param rect rectangle
* @param text text to be drawn
*/
void drawThumbnails(final MapRect rect, final byte[] text) {
// thumbnail width and height, empty line height
final double ffmax = 0.25, ffhmax = 0.5, flhmax = 0.3;
double ff = ffmax, ffh = ffhmax, flh = flhmax;
byte lhmi = (byte) Math.max(3, ffh * fontSize);
byte fhmi = (byte) Math.max(6, (flh + ffh) * fontSize);
int h = rect.h;
rect.thumbf = ff * fontSize;
rect.thumbal = 0;
final int[][] data = new FTLexer().init(text).info();
boolean l = false;
final double flhmin = 0.168, ffhmin = 0.28, ffmin = 0.14;
double flhtmin = flhmin, ffhtmax = ffhmax, ffhtmin = ffhmin, fftmax = ffmax, fftmin = ffmin;
double bflh = flhmax, bffh = ffhmax, bff = ffmax, flhtmax = flhmax;
while(rect.thumbal < 2) {
// find parameter setting for the available space
ff = round(fftmax, fftmin);
rect.thumbf = ff * fontSize;
ffh = round(ffhtmax, ffhtmin);
rect.thumbfh = (byte) Math.max(1, ffh * fontSize);
flh = round(flhtmax, flhtmin);
rect.thumblh = (byte) Math.max(1, (flh + ffh) * fontSize);
rect.thumbsw = rect.thumbf;
switch(rect.thumbal) {
case 0:
h = drawToken(rect, data, false);
break;
case 1:
h = drawSentence(rect, data, false, rect.h);
break;
}
if(h >= rect.h || le(ffmax, ff) || le(ffhmax, ffh) || le(flhmax, flh)) {
if(l) {
// use last setup to draw
rect.thumbf = bff * fontSize;
rect.thumbfh = (byte) Math.max(1, bffh * fontSize);
rect.thumblh = (byte) Math.max(1, (bflh + bffh) * fontSize);
rect.thumbsw = rect.thumbf;
switch(rect.thumbal) {
case 0:
drawToken(rect, data, true);
return;
case 1: case 2:
drawSentence(rect, data, true, rect.h);
return;
}
}
if(le(ff, ffmin) || le(ffh, ffhmin) || le(flh, flhmin)) {
// change abstraction level
rect.thumbal++;
fhmi = rect.thumbfh;
lhmi = rect.thumblh;
fftmin = ffmin;
fftmax = ffmax;
ffhtmin = ffhmin;
ffhtmax = ffhmax;
flhtmin = flhmin;
flhtmax = flhmax;
} else {
// shrink size
fftmax = ff;
ffhtmax = ffh;
flhtmax = flh;
}
} else {
l = true;
// backup and try to enlarge
bff = ff;
bffh = ffh;
bflh = flh;
fftmin = ff;
ffhtmin = ffh;
flhtmin = flh;
}
}
// calculate parameter setting
// total number of bytes
final double sum = data[3].length + data[4].length;
// number of lines printable
final double nl = (rect.h - 3.0) / lhmi;
// factor for the width of a thumbnail
final double fnew = (nl * (rect.w - 3) - data[4].length) / sum;
rect.thumbf = fnew;
rect.thumbfh = fhmi;
rect.thumblh = lhmi;
rect.thumbsw = Math.max(1, fnew);
drawSentence(rect, data, true, rect.h);
}
/**
* Draws a text using thumbnail visualization, that represents a sentence
* through a thumbnail. Sentences are separated through black thumbnails.
* @param rect rectangle
* @param data full-text to be drawn
* @param draw boolean for drawing (used for calculating the height)
* @param mh maximum height
* @return height
*/
private int drawSentence(final MapRect rect, final int[][] data, final boolean draw,
final int mh) {
final boolean sen = rect.thumbal == 1;
final FTPos ftp = rect.pos;
final int xx = rect.x;
final int ww = rect.w;
int yy = rect.y + 3;
final Color textc = color(rect.level + 4);
g.setColor(textc);
int count = -1;
int pp = 0, sl = 0, pl = 0;
int psl = 0, ppl = 0;
double error = 0;
int i = 0;
int ll = 0;
final int[] data0 = data[0], data1 = data[1], data2 = data[2];
final int dl0 = data0.length, dl1 = data1.length, dl2 = data2.length;
while(i < dl0) {
g.setColor(textc);
int ct = 0;
int wl = 0; // word and line length
while(i < dl0 && ppl < dl2 && data2[ppl] > pl && (psl >= dl1 || data1[psl] > sl)) {
sl += data0[i];
pl += data0[i];
final int lastl = (int) (data0[i] * rect.thumbf);
error += data0[i] * rect.thumbf - lastl;
if(error >= 1) {
wl += (int) error;
error -= (int) error;
}
wl += lastl;
if(ftp != null && ftp.contains(count)) {
++ct;
++pp;
}
++count;
++i;
}
if(ct == 0) {
while(ll + wl >= ww) {
if(draw) g.fillRect(xx + ll, yy, ww - ll, rect.thumbfh);
wl -= ww - ll;
ll = 0;
yy += rect.thumblh;
if(yy + rect.thumblh >= rect.y + mh) {
// height to big
return rect.h;
}
}
if(draw) g.fillRect(xx + ll, yy, wl, rect.thumbfh);
ll += wl;
} else {
int cttmp = 0;
int wltmp = wl / ct;
while(cttmp < ct) {
if(pp - ct + cttmp < ftp.size()) g.setColor(GREEN);
while(ll + wltmp >= ww) {
if(draw) g.fillRect(xx + ll, yy, ww - ll, rect.thumbfh);
wltmp -= ww - ll;
ll = 0;
yy += rect.thumblh;
// skip rest if no space is left
if(yy + rect.thumblh >= rect.y + mh) return rect.h;
}
if(draw) g.fillRect(xx + ll, yy, wltmp, rect.thumbfh);
ll += wltmp;
wltmp = wl / ct + (cttmp == ct - 2 ? wl - wl / ct * ct : 0);
++cttmp;
}
}
// new sentence
if(psl < dl1 && data1[psl] == sl) {
if(ll + rect.thumbsw >= ww) {
yy += rect.thumblh;
ll = 0;
// skip rest if no space is left
if(yy + rect.thumblh >= rect.y + mh) return rect.h;
}
if(draw) {
g.setColor(TEXT);
g.fillRect(xx + ll, yy, (int) rect.thumbsw, rect.thumbfh);
g.setColor(textc);
}
ll += rect.thumbsw;
sl = 0;
++psl;
}
// new paragraph
if(ppl < dl2 && data2[ppl] == pl) {
pl = 0;
++ppl;
if(sen) {
yy += rect.thumblh;
ll = 0;
// skip rest if no space is left
if(yy + rect.thumblh >= rect.y + mh) return rect.h;
}
}
}
return yy - rect.y + rect.thumbfh;
}
/**
* Draws a text using thumbnail visualization, that represents a token
* through a thumbnail.
* @param rect rectangle
* @param data full-text to be drawn
* @param draw boolean for drawing (used for calculating the height)
* @return heights
*/
private int drawToken(final MapRect rect, final int[][] data, final boolean draw) {
final double xx = rect.x;
final double ww = rect.w;
final FTPos ftp = rect.pos;
int yy = rect.y + 3;
double ll = 0; // line length
double e = 0;
final Color textc = color(rect.level + 4);
int count = 0;
int sl = 0, pl = 0;
int psl = 0, ppl = 0;
final int[] data0 = data[0], data1 = data[1], data2 = data[2];
final int dl1 = data1.length, dl2 = data2.length;
for(final int ad0 : data0) {
int wl = (int) (ad0 * rect.thumbf); // word length
e += ad0 * rect.thumbf - wl;
if(e >= 1) {
wl += (int) e;
e -= (int) e;
}
sl += ad0;
pl += ad0;
// check if rectangle fits in line - don't split token and dot
if(ll + wl + rect.thumbsw * (psl < dl1 && sl == data1[psl] ? 1 : 0) >= ww) {
yy += rect.thumblh;
ll = 0;
if(wl >= ww) return rect.h + 3;
}
if(draw) {
// draw word
g.setColor(ftp != null && ftp.contains(count) ? GREEN : textc);
g.fillRect((int) (xx + ll), yy, wl, rect.thumbfh);
}
ll += wl;
++count;
if(psl < dl1 && sl == data1[psl]) {
// new sentence, draw dot
if(draw) {
g.setColor(TEXT);
g.fillRect((int) (xx + ll), yy, (int) rect.thumbsw, rect.thumbfh);
g.setColor(textc);
}
ll += rect.thumbsw;
++psl;
sl = 0;
}
ll += rect.thumbf;
if(ppl < dl2 && pl == data2[ppl]) {
// new paragraph
yy += rect.thumblh;
ll = 0;
++ppl;
pl = 0;
}
}
return yy - rect.y + 3;
}
/**
* Calculates the tooltip text for the thumbnail visualization.
* @param rect rectangle
* @param data full-text to be drawn
* @param x mouseX
* @param y mouseY
* @param w width of map view
* @return token list or {@code null}
*/
TokenList computeToolTip(final MapRect rect, final int[][] data, final int x, final int y,
final int w) {
// rectangle is empty - don't need a tooltip
if(rect.thumbf == 0) return null;
final boolean sen = rect.thumbal < 2;
final boolean ds = rect.thumbal < 1;
final FTPos ftp = rect.pos;
final int ww = rect.w;
int yy = rect.y + 3;
ul = -1;
final TokenList tl = new TokenList();
ttcol = new BoolList();
int cc = 0;
int sl = 0;
int pl = 0;
int ppl = 0;
int psl = 0;
double error = 0;
double ll = 0; // line length
final int[] data0 = data[0], data1 = data[1], data2 = data[2], data3 = data[3], data4 = data[4];
final int dl0 = data0.length, dl1 = data1.length, dl2 = data2.length, dl3 = data3.length;
for(int i = 0; i < dl0; ++i) {
double wl = data0[i] * rect.thumbf;
// sum up error, caused by int cast
error += data0[i] * rect.thumbf - wl;
if(error >= 1) {
// adjust word length
wl += error;
error -= (int) error;
}
pl += data0[i];
sl += data0[i];
cc += data0[i];
// find hovered thumbnail and corresponding text
final boolean ir;
if(ll + wl + (ds && psl < dl1 && data1[psl] == sl ? rect.thumbsw : 0) >= ww) {
// split token to safe space
if(!ds) wl -= ww - ll;
yy += rect.thumblh;
ir = inRect(rect.x, yy, wl, rect.thumbfh, x, y);
ll = wl + (psl < dl1 && data1[psl] == sl ? rect.thumbsw : rect.thumbf);
} else {
ir = inRect(rect.x + ll, yy, wl, rect.thumbfh, x, y);
ll += wl + (ds ? rect.thumbf : 0);
}
if(ir) {
// go back and forward through the text, centralize the hovered token
final int si = i;
final int sp = BaseXLayout.width(g, ' ');
final int sd = BaseXLayout.width(g, '.');
// go some tokens backwards form current token
final int bpsl = data1[psl] == sl ? psl + 1 : psl;
final int bsl = data1[psl] == sl ? 0 : sl;
ll = (sd << 1) + sp;
int l;
int p = cc >= data0[i] ? cc - data0[i] : 0;
boolean apm;
while(p > -1 && i > -1) {
// append punctuation mark
apm = psl < dl1 && data1[psl] == sl;
final byte[] tok = new byte[data0[i] + (apm ? 1 : 0)];
final int ts = tok.length;
for(int k = 0; k < ts - (apm ? 1 : 0); ++k) tok[k] = (byte) data3[p + k];
if(apm) {
tok[ts - 1] = (byte) data4[psl];
++sl;
}
sl -= ts;
if(sl == 0) {
--psl;
if(psl == -1) psl = dl1;
else sl = data1[psl];
}
l = 0;
for(int n = 0; n < ts; n += cl(tok, n)) l += BaseXLayout.width(g, cp(tok, n));
if(si > i && ll + l + sp >= w / 2.0d) break;
ll += l + sp;
tl.add(tok);
// find token color
ttcol.add(ftp != null && ftp.contains(i));
if(i == 0) break;
p -= data0[i - 1];
--i;
}
if(i > 0) {
tl.add(new byte[] { '.', '.' });
ttcol.add(false);
}
i = si + 1;
p = cc;
// invert tokens
ul = tl.size() - 1;
final byte[][] toks = tl.next();
final boolean[] tc = ttcol.next();
final int tsl = toks.length;
for(int j = tsl - 1; j >= 0; j--) {
tl.add(toks[j]);
ttcol.add(tc[j]);
}
ll = 0;
sl = bsl;
psl = bpsl;
// process tokens after current token
while(p < dl3 && i < dl0) {
apm = false;
if(psl < dl1 && data1[psl] == sl + data0[i]) {
apm = true;
sl = 0;
++psl;
}
final byte[] tok = new byte[data0[i] + (apm ? 1 : 0)];
final int ts = tok.length;
l = 0;
for(int k = 0; k < ts - (apm ? 1 : 0); ++k) {
tok[k] = (byte) data3[p + k];
}
if(apm) tok[ts - 1] = (byte) data4[psl - 1];
sl += apm ? sl : ts;
for(int n = 0; n < ts; n += cl(tok, n)) l += BaseXLayout.width(g, cp(tok, n));
if(ll + l + sp + 2 * sd >= w / 2.0d) break;
ll += l + sp;
tl.add(tok);
ttcol.add(ftp != null && ftp.contains(i));
p += ts - (apm ? 1 : 0);
++i;
}
if(i < dl0) {
tl.add(new byte[] { '.', '.' });
ttcol.add(false);
}
return tl;
}
// new sentence
if(ds && psl < dl1 && data1[psl] == sl) {
if(ll + rect.thumbsw >= ww) {
yy += rect.thumblh;
ll -= ww;
}
ll += rect.thumbsw;
sl = 0;
++psl;
}
// new paragraph
if(ppl < dl2 && data2[ppl] == pl) {
pl = 0;
++ppl;
if(sen) {
yy += rect.thumblh;
ll = 0;
}
}
}
return tl;
}
/**
* Draws pre-calculated tooltip.
* @param rect view rectangle
* @param x x value
* @param y y value
* @param tl token list
*/
void drawToolTip(final MapRect rect, final int x, final int y, final TokenList tl) {
if(tl == null || tl.isEmpty()) return;
final int sw = BaseXLayout.width(g, ' ');
int wl = 0;
int nl = 1;
int wi = rect.w / 2;
final IntList len = new IntList();
final int ts = tl.size();
for(int i = 0; i < ts; i++) {
int l = 0;
final byte[] tok = tl.get(i);
final int ns = tok.length;
for(int n = 0; n < ns; n += cl(tok, n)) {
l += BaseXLayout.width(g, cp(tok, n));
}
if(wl + l + sw < wi) {
wl += l + sw;
} else {
++nl;
if(l > wi) wi = l;
wl = l + sw;
}
len.add(l);
}
final int ww = nl == 1 && wl < wi ? wl : wi;
// find optimal position for the tooltip
final int xx = x + 10 + ww >= rect.x + rect.w ? rect.x + rect.w - ww - 2 : x + 10;
int yy = y + 28 + fontSize * nl + 4 < rect.y + rect.h ? y + 28 :
y - rect.y - 4 > fontSize * nl ? y - fontSize * nl : rect.y + rect.h - 4 - fontSize * nl;
g.setColor(color(10));
g.drawRect(xx - 3, yy - fontSize - 1, ww + 3, fontSize * nl + 7);
g.setColor(color(0));
g.fillRect(xx - 2, yy - fontSize, ww + 2, fontSize * nl + 6);
g.setColor(color(20));
wl = 0;
final int is = tl.size();
for(int i = 0; i < is; ++i) {
final int l = len.get(i);
if(wl + l + sw >= wi) {
yy += fontSize + 1;
wl = 0;
}
final boolean pm = !lod(tl.get(i)[tl.get(i).length - 1]);
if(ttcol.get(i)) g.setColor(GREEN);
g.drawString(string(tl.get(i)), xx + wl, yy);
if(i == ul) {
g.drawLine(xx + wl, yy + 1, xx + wl + (pm ? l - sw : l), yy + 1);
}
g.setColor(color(24));
wl += l + sw;
}
}
/**
* Checks if cursor is inside the rectangle.
* @param rx x value
* @param ry y value
* @param rw width
* @param rh height
* @param xx x value of the cursor
* @param yy y value of the cursor
* @return result of check
*/
private static boolean inRect(final double rx, final int ry, final double rw, final int rh,
final int xx, final int yy) {
return xx >= rx && xx <= rx + rw && yy >= ry && yy <= ry + rh;
}
/**
* Checks if the first is smaller than the second value, ignoring a small difference.
* @param a double 1
* @param b double 2
* @return result of check
*/
private static boolean le(final double a, final double b) {
return a < b || a / b < 1.05;
}
/**
* Returns the rounded average of the specified values.
* @param a first double
* @param b second double
* @return rounded double
*/
private static double round(final double a, final double b) {
final double v = (a + b) / 2;
final double d = v * 100000;
final int i = (int) d;
final double r = d - i >= 0.5 ? i + 1 : i;
return r / 100000;
}
}