package org.basex.gui.dialog;
import static org.basex.core.Text.*;
import static org.basex.util.Token.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import org.basex.core.cmd.*;
import org.basex.data.*;
import org.basex.gui.*;
import org.basex.gui.layout.*;
import org.basex.gui.layout.TreeNode;
/**
* Combination of a JTree and a text field. The tree visualizes the database
* content including raw files and documents. The search field allows to
* quickly access specific files/documents.
*
* @author BaseX Team 2005-12, BSD License
* @author Lukas Kircher
*/
public class DialogResources extends BaseXBack {
/** Search text field. */
final BaseXTextField filterText;
/** Database/root node. */
final TreeFolder root;
/** Dialog reference. */
final Dialog dialog;
/** Resource tree. */
private final BaseXTree tree;
/** Filter button. */
private final BaseXButton filter;
/** Clear button. */
private final BaseXButton clear;
/**
* Constructor.
* @param dp dialog reference
*/
public DialogResources(final DialogProps dp) {
setLayout(new BorderLayout(0, 5));
dialog = dp;
// init tree - additional root node necessary to bypass
// the egg/chicken dilemma
final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
tree = new BaseXTree(rootNode, dp).border(4, 4, 4, 4);
tree.setRootVisible(false);
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
final ImageIcon xml = BaseXLayout.icon("file_xml");
final ImageIcon raw = BaseXLayout.icon("file_raw");
tree.setCellRenderer(new TreeNodeRenderer(xml, raw));
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(final TreeSelectionEvent e) {
TreeNode n = (TreeNode) e.getPath().getLastPathComponent();
String filt = n.equals(root) ? "" : n.path();
String trgt = filt + '/';
if(n.isLeaf()) {
n = (TreeNode) n.getParent();
trgt = (n == null || n.equals(root) ? "" : n.path()) + '/';
} else {
filt = trgt;
}
filterText.setText(filt);
dp.add.target.setText(trgt);
}
});
// add default children to tree
final Data data = dp.gui.context.data();
root = new TreeRootFolder(token("/"), token("/"), tree, data);
((DefaultTreeModel) tree.getModel()).insertNodeInto(root, rootNode, 0);
tree.expandPath(new TreePath(root.getPath()));
filter = new BaseXButton(FILTER, dp);
clear = new BaseXButton(CLEAR, dp);
// popup menu for node interaction
new BaseXPopup(tree, dp.gui, new DeleteCmd(), new RenameCmd());
// button panel
final BaseXBack buttons = new BaseXBack();
buttons.add(filter);
buttons.add(clear);
final BaseXBack btn = new BaseXBack().layout(new BorderLayout());
btn.add(buttons, BorderLayout.EAST);
filterText = new BaseXTextField("/", dp);
BaseXLayout.setWidth(filterText, 250);
// left panel
final BaseXBack panel = new BaseXBack(new BorderLayout());
panel.add(filterText, BorderLayout.CENTER);
panel.add(btn, BorderLayout.SOUTH);
final JScrollPane sp = new JScrollPane(tree);
BaseXLayout.setWidth(sp, 250);
//sp.setSize(190, 500);
add(sp, BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
}
/**
* Returns the current tree node selection.
* @return selected node
*/
TreeNode selection() {
final TreePath t = tree.getSelectionPath();
return t == null ? null : (TreeNode) t.getLastPathComponent();
}
/**
* Refreshes the given folder node. Removes all its children and reloads
* it afterwards.
* @param n folder
*/
private void refreshFolder(final TreeFolder n) {
if(n == null) return;
n.removeChildren();
final TreePath path = new TreePath(n.getPath());
tree.collapsePath(path);
tree.expandPath(path);
}
/**
* Reacts on user input.
* @param comp the action component
*/
void action(final Object comp) {
if(comp == filter) {
filter();
} else if(comp == clear) {
filterText.setText("/");
filterText.requestFocus();
refreshFolder(root);
}
}
/**
* Searches the tree for nodes that match the given search text.
*/
private void filter() {
final byte[] path = TreeNode.preparePath(token(filterText.getText()));
if(eq(path, SLASH)) {
refreshFolder(root);
return;
}
final Data data = dialog.gui.context.data();
// clear tree to append filtered nodes
root.removeAllChildren();
// check if there's a directory
// create a folder if there's either a raw or document folder
if(data.resources.isDir(path)) {
root.add(new TreeFolder(TreeFolder.name(path),
TreeFolder.path(path), tree, data));
}
// now add the actual files (if there are any)
final byte[] name = TreeFolder.name(path);
final byte[] sub = TreeFolder.path(path);
final TreeLeaf[] leaves = TreeFolder.leaves(
new TreeFolder(TreeFolder.name(sub), TreeFolder.path(sub), tree, data));
for(final TreeLeaf l : leaves) {
if(name.length == 0 || eq(l.name, name)) root.add(l);
}
((DefaultTreeModel) tree.getModel()).nodeStructureChanged(root);
}
/**
* Expands the tree after a node with the given path has been inserted.
* Due to lazy evaluation of the tree inserted documents/files are only
* added to the tree after the parent folder has been reloaded.
* @param p path of new node
*/
void refreshNewFolder(final String p) {
final byte[][] pathComp = split(token(p), '/');
TreeNode n = root;
for(final byte[] c : pathComp) {
// make sure folder is reloaded
if(n instanceof TreeFolder)
((TreeFolder) n).reload();
// find next child to continue with
for(int i = 0; i < n.getChildCount(); i++) {
final TreeNode ch = (TreeNode) n.getChildAt(i);
if(eq(ch.name, c)) {
// continue with the child if path component matches
n = ch;
break;
}
}
}
refreshFolder(n instanceof TreeFolder ? (TreeFolder) n :
(TreeFolder) n.getParent());
}
/**
* Custom tree cell renderer to distinguish between raw and xml leaf nodes.
* @author BaseX Team 2005-12, BSD License
* @author Lukas Kircher
*/
private static final class TreeNodeRenderer extends DefaultTreeCellRenderer {
/** Icon for xml files. */
private final Icon xmlIcon;
/** Icon for raw files. */
private final Icon rawIcon;
/**
* Constructor.
* @param xml xml icon
* @param raw raw icon
*/
TreeNodeRenderer(final Icon xml, final Icon raw) {
xmlIcon = xml;
rawIcon = raw;
}
@Override
public Component getTreeCellRendererComponent(final JTree tree,
final Object val, final boolean sel, final boolean exp,
final boolean leaf, final int row, final boolean focus) {
super.getTreeCellRendererComponent(tree, val, sel, exp, leaf, row, focus);
if(leaf) setIcon(((TreeLeaf) val).raw ? rawIcon : xmlIcon);
return this;
}
}
/** GUI commands for popup menu. */
abstract static class BaseCmd implements GUICommand {
@Override
public boolean checked() {
return false;
}
@Override
public String help() {
return null;
}
@Override
public String key() {
return null;
}
}
/** Delete command. */
final class DeleteCmd extends BaseCmd {
@Override
public void execute(final GUI g) {
final TreeNode n = selection();
if(n == null || !Dialog.confirm(dialog.gui, DELETE_NODES)) return;
final Runnable run = new Runnable() {
@Override
public void run() {
refreshNewFolder(n.path());
}
};
DialogProgress.execute(dialog, "", run, new Delete(n.path()));
}
@Override
public String label() {
return DELETE + DOTS;
}
@Override
public void refresh(final GUI gui, final AbstractButton button) {
final TreeNode n = selection();
button.setEnabled(n != null && !n.equals(root));
}
}
/** Rename command. */
final class RenameCmd extends BaseCmd {
@Override
public void execute(final GUI g) {
final TreeNode n = selection();
if(n == null) return;
final DialogInput d = new DialogInput(n.path(), RENAME, dialog.gui, 0);
if(!d.ok()) return;
final String p = string(TreeNode.preparePath(token(d.input())));
final Runnable run = new Runnable() {
@Override
public void run() {
refreshNewFolder(p);
}
};
DialogProgress.execute(dialog, "", run, new Rename(n.path(), p));
}
@Override
public String label() {
return RENAME + DOTS;
}
@Override
public void refresh(final GUI gui, final AbstractButton button) {
final TreeNode n = selection();
button.setEnabled(n != null && !n.equals(root));
}
}
}