package org.basex.gui.view;
import static org.basex.core.Text.*;
import static org.basex.gui.GUIConstants.*;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.StringTokenizer;
import org.basex.core.*;
import org.basex.gui.AGUI;
import org.basex.gui.GUIConstants;
import org.basex.gui.GUIConstants.Fill;
import org.basex.gui.GUIProp;
import org.basex.gui.layout.BaseXBack;
import org.basex.gui.layout.BaseXLayout;
import org.basex.util.Performance;
import org.basex.util.Token;
import org.basex.util.Util;
/**
* This class manages all visible and invisible views and allows drag and
* drop operations inside the panel.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class ViewContainer extends BaseXBack implements Runnable {
/** Dragging stroke. */
private static final BasicStroke STROKE = new BasicStroke(2);
/** Thread counter. */
private static final int[] STEPS = {
255, 225, 202, 180, 185, 165, 147, 130, 115, 100, 87, 75, 65, 55, 47,
40, 35, 30, 25, 20, 17, 15, 12, 10, 7, 5, 5, 2, 2, 2, 0, 0
};
/** Thread counter. */
private int count;
/** Orientation enumerator. */
private enum Target {
/** North orientation. */ NORTH,
/** West orientation. */ WEST,
/** East orientation. */ EAST,
/** South orientation. */ SOUTH
}
/** Reference to main window. */
private final AGUI gui;
/** States if component was moved out of the window. */
private boolean out;
/** View Layout. */
private ViewAlignment layout;
/** View panels. */
private final ViewPanel[] views;
/** Source View. */
private ViewPanel source;
/** Target View. */
private ViewPanel target;
/** Logo reference. */
private final Image logo;
/** Target orientation. */
private Target orient;
/** Temporary mouse position. */
private Point sp;
/** Temporary rectangle position. */
private final int[] pos = new int[4];
/**
* Constructor.
* @param main reference to the main window
* @param v view panels
*/
public ViewContainer(final AGUI main, final View... v) {
layout(new BorderLayout()).mode(Fill.PLAIN);
logo = BaseXLayout.image("logo");
setBackground(Color.white);
views = new ViewPanel[v.length];
for(int i = 0; i < v.length; ++i) views[i] = new ViewPanel(v[i]);
gui = main;
// build layout or use default if something goes wrong
if(!buildLayout(gui.gprop.get(GUIProp.VIEWS)) && !buildLayout(VIEWS)) {
Util.errln(Util.name(this) + ": could not build layout \"%\"", VIEWS);
}
}
/**
* Updates and validates the views.
*/
public void updateViews() {
removeAll();
layout.setVisibility(gui.context.data() != null);
layout.createView(this);
validate();
repaint();
gui.gprop.set(GUIProp.VIEWS, layout.layoutString());
}
@Override
public void run() {
Performance.sleep(1000);
while(++count < STEPS.length - 1) {
Performance.sleep(50);
if(getComponentCount() == 0) repaint();
}
}
@Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
if(getComponentCount() != 0) return;
final int w = getWidth();
final int h = getHeight();
final int hh = Math.max(220, Math.min(700, h));
final Insets i = getInsets();
if(gui.gprop.is(GUIProp.GRADIENT)) {
BaseXLayout.fill(g, WHITE, color1,
i.left, i.top, w - i.right, h - i.bottom);
}
if(w < 150 || h < 160) return;
final int lh = logo.getHeight(this);
final int y = (hh - lh - 60) / 2;
g.drawImage(logo, (w - logo.getWidth(this)) / 2, y, this);
if(w < 200 || h < 200) return;
g.setColor(new Color(0, 0, 0, 255 - STEPS[count]));
g.setFont(getFont().deriveFont(22f));
BaseXLayout.drawCenter(g, VERSINFO + ' ' + Prop.VERSION, w, y + 30 + lh);
}
/**
* Drags the current view.
* @param panel panel to be dragged
* @param p absolute mouse position
*/
void dragPanel(final ViewPanel panel, final Point p) {
source = panel;
sp = p;
calc();
repaint();
}
/**
* Drops a view and reorganizes the layout.
*/
void dropPanel() {
if(source == null) return;
if(out) {
source.delete();
} else if(orient != null) {
if(layout.delete(source) && !(layout.comp[0] instanceof ViewPanel))
layout = (ViewAlignment) layout.comp[0];
if(target == null) layout = addView(layout);
else add(layout);
updateViews();
}
source = null;
repaint();
}
/**
* Adds the dragged view after if it has been removed.
* @param lay layout instance
* @return true if component was successfully added
*/
private boolean add(final ViewAlignment lay) {
for(int o = 0; o < lay.comp.length; ++o) {
final ViewLayout comp = lay.comp[o];
if(comp instanceof ViewAlignment) {
if(add((ViewAlignment) comp)) return true;
} else if(comp == target) {
final boolean west = orient == Target.WEST;
final boolean east = orient == Target.EAST;
if(orient == Target.NORTH || west) {
if(lay.horiz == west) {
lay.add(source, o);
} else {
final ViewAlignment l = new ViewAlignment(west);
l.add(source);
l.add(target);
lay.comp[o] = l;
}
} else if(orient == Target.SOUTH || east) {
if(lay.horiz == east) {
lay.add(source, o + 1);
} else {
final ViewAlignment l = new ViewAlignment(east);
l.add(target);
l.add(source);
lay.comp[o] = l;
}
}
return true;
}
}
return false;
}
/**
* Adds the dragged view into the specified layout instance.
* @param lay layout instance
* @return resulting layout
*/
private ViewAlignment addView(final ViewAlignment lay) {
final boolean west = orient == Target.WEST;
final boolean east = orient == Target.EAST;
ViewAlignment l = lay;
if(orient == Target.NORTH || west) {
if(l.horiz == west) {
l.add(source, 0);
} else {
final ViewAlignment ll = new ViewAlignment(west);
ll.add(source);
ll.add(l);
l = ll;
}
} else if(orient == Target.SOUTH || east) {
if(l.horiz == east) {
l.add(source);
} else {
final ViewAlignment ll = new ViewAlignment(east);
ll.add(l);
ll.add(source);
l = ll;
}
}
return l;
}
/**
* Finds the view under the mouse position and returns it as possible
* target for dropping the dragged view.
* @return view container
*/
private ViewPanel getTarget() {
for(final ViewPanel v : views) {
if(v.isVisible() && new Rectangle(absLoc(v), v.getSize()).contains(sp))
return v;
}
return null;
}
/**
* Calculates the absolute location for the specified point and component.
* @param comp component
* @return absolute location
*/
private Point absLoc(final Component comp) {
Component c = comp;
final Point p = c.getLocation();
do {
c = c.getParent();
p.x += c.getX();
p.y += c.getY();
} while(c.getParent() != this);
return p;
}
@Override
public void paint(final Graphics g) {
super.paint(g);
if(source == null) return;
out = sp.x < 0 || sp.y < 0 || sp.x > getWidth() || sp.y > getHeight();
final Point p = absLoc(source);
((Graphics2D) g).setStroke(STROKE);
if(!out) {
g.setColor(color(10));
g.drawRect(p.x, p.y, source.getWidth() - 1, source.getHeight() - 1);
}
final int ac = AlphaComposite.SRC_OVER;
if(out) {
g.setColor(colormark3);
((Graphics2D) g).setComposite(AlphaComposite.getInstance(ac, 0.3f));
g.fillRect(p.x, p.y, source.getWidth(), source.getHeight());
} else if(orient != null) {
g.setColor(color(16));
g.drawRect(pos[0], pos[1], pos[2] - 1, pos[3] - 1);
((Graphics2D) g).setComposite(AlphaComposite.getInstance(ac, 0.3f));
g.setColor(color(8));
g.fillRect(pos[0], pos[1], pos[2], pos[3]);
}
}
/**
* Calculates the target position.
*/
private void calc() {
final int hh = getHeight();
final int ww = getWidth();
pos[0] = 1;
pos[1] = 1;
pos[2] = ww - 2;
pos[3] = hh - 2;
orient = null;
target = getTarget();
// paint panel which is currently moved somewhere else
if(target != null && target != source) {
final Rectangle tr = new Rectangle(absLoc(target),
target.getSize());
final int minx = tr.width >> 1;
final int miny = tr.height >> 1;
if(Math.abs(tr.x + tr.width / 2 - sp.x) < tr.width / 3) {
if(sp.y > tr.y && sp.y < tr.y + miny) {
pos[0] = tr.x;
pos[1] = tr.y;
pos[2] = tr.width;
pos[3] = miny;
orient = Target.NORTH;
} else if(sp.y > tr.y + tr.height - miny && sp.y < tr.y + tr.height) {
pos[0] = tr.x;
pos[1] = tr.y + tr.height - miny;
pos[2] = tr.width;
pos[3] = miny;
orient = Target.SOUTH;
}
} else if(Math.abs(tr.y + tr.height / 2 - sp.y) < tr.height / 3) {
if(sp.x > tr.x && sp.x < tr.x + minx) {
pos[0] = tr.x;
pos[1] = tr.y;
pos[2] = minx;
pos[3] = tr.height;
orient = Target.WEST;
} else if(sp.x > tr.x + tr.width - minx && sp.x < tr.x + tr.width) {
pos[0] = tr.x + tr.width - minx;
pos[1] = tr.y;
pos[2] = minx;
pos[3] = tr.height;
orient = Target.EAST;
}
}
}
if(orient == null) {
final int minx = ww >> 2;
final int miny = hh >> 2;
target = null;
if(sp.y < miny) {
pos[3] = miny;
orient = Target.NORTH;
} else if(sp.y > hh - miny) {
pos[3] = miny;
pos[1] = hh - miny;
orient = Target.SOUTH;
} else if(sp.x < minx) {
pos[2] = minx;
orient = Target.WEST;
} else if(sp.x > ww - minx) {
pos[2] = minx;
pos[0] = ww - minx;
orient = Target.EAST;
}
}
}
/**
* Builds the view layout by parsing the layout string.
* @param cnstr layout string
* @return true if everything went alright
*/
private boolean buildLayout(final String cnstr) {
try {
int nv = 0;
layout = null;
int lvl = -1;
final ViewAlignment[] l = new ViewAlignment[16];
final StringTokenizer st = new StringTokenizer(cnstr);
while(st.hasMoreTokens()) {
final String t = st.nextToken();
if(Token.eq(t, "H", "V")) {
l[lvl + 1] = new ViewAlignment(t.equals("H"));
if(layout == null) {
layout = l[0];
} else {
l[lvl].add(l[lvl + 1]);
}
++lvl;
} else if(t.equals("-")) {
--lvl;
} else {
final ViewPanel view = getView(t);
if(view == null) return false;
l[lvl].add(view);
++nv;
}
}
if(nv == views.length) return true;
Util.errln(Util.name(this) + ": initializing views: " + cnstr);
} catch(final Exception ex) {
Util.debug(ex);
Util.errln(Util.name(this) + ": could not build layout: " + cnstr);
}
return false;
}
/**
* Returns the view specified by its internal name. The view names
* are specified in the {@link GUIConstants} class.
* @param name name of the view
* @return found view container
*/
private ViewPanel getView(final String name) {
for(final ViewPanel view : views) {
if(view.toString().equals(name)) return view;
}
Util.debug(Util.name(this) + ": Unknown view \"%\"", name);
return null;
}
}