package org.basex.gui.view.folder; import static org.basex.gui.GUIConstants.*; import static org.basex.gui.layout.BaseXKeys.*; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Polygon; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.image.BufferedImage; import java.util.Arrays; import javax.swing.SwingUtilities; import org.basex.data.Data; import org.basex.data.Nodes; import org.basex.gui.GUIProp; import org.basex.gui.layout.BaseXBar; import org.basex.gui.layout.BaseXLayout; import org.basex.gui.layout.BaseXPopup; import org.basex.gui.view.View; import org.basex.gui.view.ViewData; import org.basex.gui.view.ViewNotifier; /** * This view offers a folder visualization of the database contents. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class FolderView extends View { /** References closed nodes. */ boolean[] opened; /** Line Height. */ int lineH; /** Focused folder position. */ int focusedPos; /** Closed Box. */ private BufferedImage closedBox; /** Opened Box. */ private BufferedImage openedBox; /** Scroll Bar. */ private final BaseXBar scroll; /** Vertical mouse position. */ private int totalW; /** Start y value. */ private int startY; /** Total height. */ private int treeH; /** Box Size. */ private int boxW; /** Box Margin. */ private int boxMargin; /** * Default constructor. * @param man view manager */ public FolderView(final ViewNotifier man) { super(FOLDERVIEW, man); createBoxes(); layout(new BorderLayout()); scroll = new BaseXBar(this); add(scroll, BorderLayout.EAST); new BaseXPopup(this, POPUP); } @Override public void refreshInit() { scroll.pos(0); if(gui.context.data() == null) { opened = null; } else if(visible()) { refreshOpenedNodes(); refreshHeight(); repaint(); } } /** * Refreshes opened nodes. */ private void refreshOpenedNodes() { final Data data = gui.context.data(); opened = new boolean[data.meta.size]; final int is = data.meta.size; for(int pre = 0; pre < is; ++pre) { opened[pre] = data.parent(pre, data.kind(pre)) <= 0; } } @Override public void refreshFocus() { repaint(); } @Override public void refreshMark() { final int pre = gui.context.focused; if(pre == -1) return; // jump to the currently marked node final Data data = gui.context.data(); final int par = data.parent(pre, data.kind(pre)); // open node if it's not visible jumpTo(pre, par != -1 && !opened[par]); repaint(); } @Override public void refreshContext(final boolean more, final boolean quick) { startY = 0; scroll.pos(0); final Nodes curr = gui.context.current(); if(more && curr.size() != 0) jumpTo(curr.list[0], true); refreshHeight(); repaint(); } @Override public void refreshLayout() { createBoxes(); if(opened == null) return; refreshOpenedNodes(); refreshHeight(); repaint(); } @Override public void refreshUpdate() { if(opened == null) return; final Data data = gui.context.data(); if(opened.length < data.meta.size) opened = Arrays.copyOf(opened, data.meta.size); startY = 0; scroll.pos(0); final Nodes marked = gui.context.marked; if(marked.size() != 0) jumpTo(marked.list[0], true); refreshHeight(); repaint(); } @Override public boolean visible() { return gui.gprop.is(GUIProp.SHOWFOLDER); } @Override public void visible(final boolean v) { gui.gprop.set(GUIProp.SHOWFOLDER, v); } @Override protected boolean db() { return true; } /** * Refreshes tree height. */ private void refreshHeight() { if(opened == null) return; treeH = new FolderIterator(this).height(); scroll.height(treeH + 5); } @Override public void paintComponent(final Graphics g) { if(opened == null) { refreshInit(); return; } super.paintComponent(g); if(opened == null) return; gui.painting = true; startY = -scroll.pos(); totalW = getWidth() - (treeH > getHeight() ? scroll.getWidth() : 0); final FolderIterator it = new FolderIterator(this, startY + 5, getHeight()); final Data data = gui.context.data(); while(it.more()) { final int kind = data.kind(it.pre); final boolean elem = kind == Data.ELEM || kind == Data.DOC; final int x = 8 + it.level * (lineH >> 1) + (elem ? lineH : boxW); drawString(g, it.pre, x, it.y + boxW); } gui.painting = false; } /** * Draws a string and checks mouse position. * @param g graphics reference * @param pre pre value * @param x horizontal coordinate * @param y vertical coordinate */ private void drawString(final Graphics g, final int pre, final int x, final int y) { final Data data = gui.context.data(); final Nodes marked = gui.context.marked; final int kind = data.kind(pre); final boolean elem = kind == Data.ELEM || kind == Data.DOC; Color col = Color.black; Font fnt = font; if(marked.find(pre) >= 0) { // mark node col = colormark3; fnt = bfont; } if(y < -lineH) return; g.setColor(color1); g.drawLine(2, y + boxMargin - 1, totalW - 5, y + boxMargin - 1); final byte[] name = ViewData.content(data, pre, false); int p = gui.context.focused; while(p > pre) p = ViewData.parent(data, p); if(pre == p) { g.setColor(color2); g.fillRect(0, y - boxW - boxMargin, totalW, lineH + 1); } if(elem) { final boolean large = gui.gprop.num(GUIProp.FONTSIZE) > 20; final int yy = y - boxW - (large ? 6 : 3); final Image box = opened[pre] ? openedBox : closedBox; g.drawImage(box, x - lineH, yy, this); } g.setFont(fnt); g.setColor(col); final int tw = totalW + 6; final int fsz = gui.gprop.num(GUIProp.FONTSIZE); BaseXLayout.chopString(g, name, x, y - fsz, tw - x - 10, fsz); if(gui.context.focused == pre) { g.setColor(color4); g.drawRect(1, y - boxW - boxMargin, totalW - 3, lineH + 1); g.drawRect(2, y - boxW - boxMargin + 1, totalW - 5, lineH - 1); } } /** * Focuses the current pre value. * @param x x mouse position * @param y y mouse position * @return currently focused id */ private boolean focus(final int x, final int y) { if(opened == null) return false; final FolderIterator it = new FolderIterator(this, startY + 3, getHeight()); final Data data = gui.context.data(); while(it.more()) { if(y > it.y && y <= it.y + lineH) { Cursor c = CURSORARROW; final int kind = data.kind(it.pre); if(kind == Data.ELEM || kind == Data.DOC) { // set cursor when moving over tree boxes final int xx = 8 + it.level * (lineH >> 2) + lineH; if(x > xx - 20 && x < xx) c = CURSORHAND; } gui.cursor(c); gui.notify.focus(it.pre, this); repaint(); return true; } } return false; } /** * Jumps to the specified pre value. * @param pre pre value to be found * @param open opened folder */ private void jumpTo(final int pre, final boolean open) { if(getWidth() == 0 || !visible()) return; if(open) { int p = pre; while(p > 0) { opened[p] = true; p = ViewData.parent(gui.context.data(), p); } refreshHeight(); } // find specified pre value final FolderIterator it = new FolderIterator(this); while(it.more() && pre != it.pre); // set new vertical position final int y = -it.y; final int h = getHeight(); if(y > startY || y + h < startY + lineH) { startY = Math.min(0, Math.max(-treeH + h - 5, y + lineH)); scroll.pos(-startY); } } /** * Creates click boxes. */ private void createBoxes() { final int s = gui.gprop.num(GUIProp.FONTSIZE); boxMargin = s >> 2; lineH = s + boxMargin; boxW = s - boxMargin; final int sp = Math.max(1, s >> 4); /* Empty Box. */ final BufferedImage emptyBox = new BufferedImage(boxW + 1, boxW + 1, BufferedImage.TYPE_INT_ARGB); Graphics2D g = emptyBox.createGraphics(); smooth(g); g.setColor(color4); g.fillOval((boxW >> 2) - 1, (boxW >> 2) + 1, boxW >> 1, boxW >> 1); g.setColor(color3); g.fillOval((boxW >> 2) - 2, boxW >> 2, boxW >> 1, boxW >> 1); openedBox = new BufferedImage(boxW + 1, boxW + 1, BufferedImage.TYPE_INT_ARGB); g = openedBox.createGraphics(); smooth(g); Polygon p = new Polygon(new int[] { 0, boxW, boxW >> 1 }, new int[] { boxW - sp >> 1, boxW - sp >> 1, boxW }, 3); p.translate(0, -1); g.setColor(color4); g.fillPolygon(p); p.translate(-1, -1); g.setColor(color3); g.fillPolygon(p); closedBox = new BufferedImage(boxW + 1, boxW + 1, BufferedImage.TYPE_INT_ARGB); g = closedBox.createGraphics(); smooth(g); p = new Polygon(new int[] { boxW - sp >> 1, boxW, boxW - sp >> 1 }, new int[] { 0, boxW >> 1, boxW }, 3); p.translate(-1, 1); g.setColor(color4); g.fillPolygon(p); p.translate(-1, -1); g.setColor(color3); g.fillPolygon(p); } @Override public void mouseMoved(final MouseEvent e) { if(gui.updating) return; super.mouseMoved(e); // set new focus focus(e.getX(), e.getY()); } @Override public void mousePressed(final MouseEvent e) { if(gui.updating || opened == null) return; super.mousePressed(e); if(!focus(e.getX(), e.getY())) return; // add or remove marked node final Nodes marked = gui.context.marked; if(e.getClickCount() == 2) { gui.notify.context(marked, false, null); } else if(e.isShiftDown()) { gui.notify.mark(1, null); } else if(sc(e)) { gui.notify.mark(2, null); } else if(!SwingUtilities.isLeftMouseButton(e) || getCursor() != CURSORHAND) { if(!marked.contains(gui.context.focused)) gui.notify.mark(0, null); } else { // open/close entry opened[gui.context.focused] ^= true; refreshHeight(); repaint(); } } @Override public void mouseDragged(final MouseEvent e) { final boolean left = SwingUtilities.isLeftMouseButton(e); if(!left || gui.updating || opened == null) return; super.mouseDragged(e); // marks currently focused node if(focus(e.getX(), e.getY())) gui.notify.mark(1, null); } @Override public void mouseWheelMoved(final MouseWheelEvent e) { if(gui.updating) return; scroll.pos(scroll.pos() + e.getUnitsToScroll() * 20); repaint(); } @Override public void keyPressed(final KeyEvent e) { if(gui.updating || opened == null) return; super.keyPressed(e); int focus = focusedPos == -1 ? 0 : focusedPos; if(gui.context.focused == -1) gui.context.focused = 0; final int focusPre = gui.context.focused; final Data data = gui.context.data(); final int kind = data.kind(focusPre); final boolean right = NEXT.is(e); boolean down = NEXTLINE.is(e); boolean up = PREVLINE.is(e); if(right || PREV.is(e)) { // open/close subtree if(e.isShiftDown()) { opened[focusPre] = right; final int s = data.meta.size; for(int pre = focusPre + 1; pre != s && data.parent(pre, data.kind(pre)) >= focusPre; pre++) { opened[pre] = right; } refreshHeight(); repaint(); return; } if(right ^ opened[focusPre] && (!ViewData.leaf(gui.gprop, data, focusPre) || data.attSize(focusPre, kind) > 1)) { opened[focusPre] = right; refreshHeight(); repaint(); } else if(right) { down = true; } else { up = true; } } if(down) { focus = Math.min(data.meta.size - 1, focus + 1); } else if(up) { focus = Math.max(0, focus - 1); } else if(NEXTPAGE.is(e)) { focus = Math.min(data.meta.size - 1, focus + getHeight() / lineH); } else if(PREVPAGE.is(e)) { focus = Math.max(0, focus - getHeight() / lineH); } else if(TEXTSTART.is(e)) { focus = 0; } else if(TEXTEND.is(e)) { focus = data.meta.size - 1; } if(focus == focusedPos) return; // calculate new tree position gui.context.focused = -1; final Nodes curr = gui.context.current(); int pre = curr.list[0]; final FolderIterator it = new FolderIterator(this); while(it.more() && focus-- != 0) pre = it.pre; if(pre == curr.list[0] && down) ++pre; gui.notify.focus(pre, this); jumpTo(pre, false); repaint(); } }