package org.basex.gui.view.table;
import static org.basex.core.Text.*;
import static org.basex.gui.GUIConstants.*;
import static org.basex.gui.layout.BaseXKeys.*;
import static org.basex.util.Token.*;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.SwingUtilities;
import org.basex.data.Data;
import org.basex.data.Nodes;
import org.basex.gui.GUIProp;
import org.basex.gui.GUIConstants.Fill;
import org.basex.gui.layout.BaseXBar;
import org.basex.gui.layout.BaseXLayout;
import org.basex.gui.layout.BaseXPanel;
import org.basex.gui.view.table.TableData.TableCol;
/**
* This is the header of the table view.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
final class TableHeader extends BaseXPanel {
/** View reference. */
final TableView view;
/** Table Data. */
final TableData tdata;
/** Temporary Input Box. */
TableInput box;
/** Current input column. */
int inputCol = -1;
/** Header flag. */
private boolean header;
/** Clicked column. */
private int clickCol = -1;
/** Moved column. */
private int moveC = -1;
/** Moved X position. */
private int mouseX = -1;
/**
* Constructor.
* @param v view reference
*/
TableHeader(final TableView v) {
super(v.gui);
mode(Fill.NONE).setFocusable(true);
tdata = v.tdata;
view = v;
BaseXLayout.setHeight(this, gui.gprop.num(GUIProp.FONTSIZE) + 8 << 1);
addMouseListener(this);
addMouseMotionListener(this);
addKeyListener(this);
// restore default focus traversal with TAB key
setFocusTraversalKeysEnabled(false);
addFocusListener(new FocusAdapter() {
@Override
public void focusGained(final FocusEvent e) {
filter(e.getOppositeComponent() instanceof TableView ? 0 :
tdata.cols.length - 1);
}
@Override
public void focusLost(final FocusEvent e) {
// tab key pressed..
if(box != null) box.stop();
inputCol = -1;
repaint();
}
});
}
@Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
g.setFont(font);
g.setColor(Color.black);
if(tdata.rows == null) {
BaseXLayout.drawCenter(g, NO_DATA, getWidth(), getHeight() / 2);
return;
}
final int fsz = gui.gprop.num(GUIProp.FONTSIZE);
final int bs = BaseXBar.SIZE;
int w = getWidth();
final int h = getHeight();
final int hh = h >> 1;
g.setColor(color2);
g.drawLine(0, h - 1, w, h - 1);
w -= bs;
double x = 0;
final int nc = tdata.cols.length;
for(int n = 0; n < nc; ++n) {
final double cw = w * tdata.cols[n].width;
final double ce = x + cw;
// header
final boolean clicked = n == clickCol && moveC == -1 && header;
BaseXLayout.drawCell(g, (int) x, (int) ce + 1, 0, hh, clicked);
// input field
g.setColor(Color.white);
g.fillRect((int) x + 1, hh, (int) ce - (int) x - 2, hh - 2);
g.drawLine((int) ce - 1, hh - 1, (int) ce - 1, h - 2);
g.setColor(GRAY);
g.drawLine((int) ce, hh - 1, (int) ce, h - 2);
// draw headers
g.setColor(Color.black);
g.setFont(bfont);
final int off = clicked ? 1 : 0;
BaseXLayout.chopString(g, tdata.cols[n].name,
(int) x + 4 + off, 2 + off, (int) cw, fsz);
if(n == tdata.sortCol) {
if(tdata.asc) g.fillPolygon(new int[] { (int) ce - 9 + off,
(int) ce - 3 + off, (int) ce - 6 + off },
new int[] { 4 + off, 4 + off, 8 + off }, 3);
else g.fillPolygon(new int[] { (int) ce - 9 + off, (int) ce - 3 + off,
(int) ce - 6 + off }, new int[] { 8 + off, 8 + off, 4 + off }, 3);
}
// draw filter texts
if(box != null && inputCol == n) {
box.paint(g, (int) x, hh, (int) ce - (int) x, hh);
} else {
g.setColor(Color.black);
g.setFont(font);
g.drawString(tdata.cols[n].filter, (int) x + 5, h - 7);
}
x = ce;
}
final boolean clicked = nc == clickCol;
BaseXLayout.drawCell(g, (int) x, w + bs, 0, hh, clicked && header);
BaseXLayout.drawCell(g, (int) x, w + bs, hh - 1, h, clicked && !header);
g.setColor(Color.black);
g.setFont(bfont);
smooth(g);
int o = header && clicked ? 1 : 0;
g.fillPolygon(new int[] { (int) x + o + 4, (int) x + o + bs - 4,
(int) x + o + bs / 2 }, new int[] { o + 6, o + 6, o + bs - 3 }, 3);
o = !header && clicked ? 1 : 0;
final byte[] reset = { 'x' };
x += (bs - BaseXLayout.width(g, reset)) / 2d;
BaseXLayout.chopString(g, reset, (int) x + o, hh + o + 1, w, fsz);
}
@Override
public void mouseMoved(final MouseEvent e) {
if(tdata.rows == null) return;
Cursor cursor = CURSORARROW;
mouseX = e.getX();
final int w = getWidth() - BaseXBar.SIZE;
if(header(e.getY())) {
moveC = colSep(w, mouseX);
if(moveC != -1) cursor = CURSORMOVEH;
} else {
moveC = -1;
if(mouseX < w) cursor = CURSORTEXT;
if(gui.gprop.is(GUIProp.MOUSEFOCUS)) {
final int c = tdata.column(w, mouseX);
if(c != -1) filter(c);
}
}
view.gui.cursor(cursor);
}
/**
* Returns the column separator at the specified horizontal position.
* @param w panel width
* @param mx mouse position
* @return column
*/
private int colSep(final int w, final int mx) {
double x = 0;
for(int i = 0; i < tdata.cols.length; ++i) {
if(i > 0 && Math.abs(mx - x) < 3) return i;
x += w * tdata.cols[i].width;
}
return -1;
}
@Override
public void mouseDragged(final MouseEvent e) {
if(tdata.rows == null) return;
if(moveC != -1) {
final int x = e.getX();
final double p = (double) (x - mouseX) / (getWidth() - BaseXBar.SIZE);
final double[] ww = new double[tdata.cols.length];
for(int w = 0; w < ww.length; ++w) ww[w] = tdata.cols[w].width;
if(e.isShiftDown()) {
ww[moveC - 1] += p;
ww[moveC] -= p;
} else {
for(int i = 0; i < moveC; ++i) ww[i] += p / moveC;
for(int i = moveC; i < ww.length; ++i) ww[i] -= p / (ww.length - moveC);
}
for(final double w : ww) if(w < 0.0001) return;
mouseX = x;
for(int w = 0; w < ww.length; ++w) tdata.cols[w].width = ww[w];
} else if(clickCol != -1) {
int c = tdata.column(getWidth() - BaseXBar.SIZE, e.getX());
if(c == -1) c = tdata.cols.length;
if(c != clickCol || header != header(e.getY())) clickCol = -1;
}
view.repaint();
}
@Override
public void mouseExited(final MouseEvent e) {
if(tdata.rows == null) return;
view.gui.cursor(CURSORARROW);
clickCol = -1;
}
@Override
public void mousePressed(final MouseEvent e) {
if(tdata.rows == null || !SwingUtilities.isLeftMouseButton(e)) return;
clickCol = tdata.column(getWidth() - BaseXBar.SIZE, mouseX);
if(clickCol == -1) clickCol = tdata.cols.length;
header = header(e.getY());
repaint();
}
@Override
public void mouseReleased(final MouseEvent e) {
if(tdata.rows == null) return;
if(!SwingUtilities.isLeftMouseButton(e)) {
chooseCols(e);
} else {
if(clickCol == -1) return;
// header
if(header(e.getY())) {
if(moveC == -1) {
if(clickCol == tdata.cols.length) {
chooseRoot(e);
} else {
// sort data in current column
view.gui.cursor(CURSORWAIT);
tdata.asc = tdata.sortCol != clickCol || !tdata.asc;
tdata.sortCol = clickCol;
tdata.sort();
view.gui.cursor(CURSORARROW, true);
}
}
} else {
// activate table filter
if(clickCol != tdata.cols.length) {
filter(clickCol);
} else {
// reset table filter
tdata.resetFilter();
view.query();
}
}
}
clickCol = -1;
view.repaint();
}
/**
* Shows a popup menu to choose main category to be displayed.
* @param e event reference
*/
private void chooseRoot(final MouseEvent e) {
if(tdata.roots.size() == 0) return;
final Data data = view.gui.context.data();
final JPopupMenu popup = new JPopupMenu();
final byte[] root = data.tagindex.key(tdata.root);
for(final byte[] en : tdata.roots) {
final int id = data.tagindex.id(en);
final JMenuItem mi = new JRadioButtonMenuItem(string(en), eq(root, en));
mi.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent ac) {
tdata.init(data, id);
view.refreshContext(true, false);
}
});
popup.add(mi);
}
popup.show(this, e.getX(), e.getY());
}
/**
* Shows a popup menu to filter the visible columns.
* @param e event reference
*/
private void chooseCols(final MouseEvent e) {
final JPopupMenu popup = new JPopupMenu();
for(final TableCol col : tdata.cols) {
final String item = (col.elem ? "" : "@") + string(col.name);
final JMenuItem mi = new JCheckBoxMenuItem(item, col.width != 0);
mi.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent ac) {
final boolean sel = mi.isSelected();
boolean vis = sel;
// disallow removal of last visible column
for(final TableCol c : tdata.cols) vis |= c != col && c.width != 0;
if(vis) {
col.hwidth = sel ? 0 : col.width;
col.width = sel ? col.hwidth : 0;
} else {
mi.setSelected(true);
}
popup.setVisible(true);
tdata.setWidths(true);
view.refreshContext(true, true);
}
});
popup.add(mi);
}
popup.show(this, e.getX(), e.getY());
}
/**
* Checks if specified y value lies in table header.
* @param y position
* @return true for table header, false for input field
*/
private boolean header(final int y) {
return y < getHeight() >> 1;
}
/**
* Handles the filter columns.
* @param col current column
*/
void filter(final int col) {
// activate table filter
if(inputCol != col) {
if(box != null) box.stop();
box = new TableInput(this, tdata.cols[col].filter);
inputCol = col;
}
requestFocusInWindow();
}
@Override
public void keyPressed(final KeyEvent e) {
if(tdata.roots.size() == 0 || box == null || control(e) || inputCol == -1)
return;
if(ENTER.is(e)) {
box.stop();
inputCol = -1;
final Nodes marked = view.gui.context.marked;
if(marked.size() != 0) view.gui.notify.context(marked, false, null);
} else if(TAB.is(e)) {
tdata.cols[inputCol].filter = box.text;
box.stop();
final int in = inputCol + (e.isShiftDown() ? -1 : 1);
if(in < 0) {
transferFocusBackward();
} else if(in == tdata.cols.length) {
transferFocus();
} else {
inputCol = in;
box = new TableInput(this, tdata.cols[inputCol].filter);
}
} else {
box.code(e);
}
repaint();
}
@Override
public void keyTyped(final KeyEvent e) {
if(tdata.roots.size() == 0 || box == null || inputCol == -1 ||
control(e) || !box.add(e)) return;
tdata.cols[inputCol].filter = box.text;
view.query();
}
}