package org.openslx.dozmod.gui.control;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.Location;
import org.openslx.dozmod.Config;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.helper.GridManager;
import org.openslx.dozmod.thrift.cache.MetaDataCache;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
/**
*
* Widget to select the locations of a lecture. The UI is simply two JLists and
* a button panel to move the elements from one list to the other. Use the
* getters to read the values of the lists/checkbox: get
*
* @author Jonathan Bauer
*
*/
@SuppressWarnings("serial")
public class LocationSelector extends JPanel {
private final static Logger LOGGER = Logger
.getLogger(LocationSelector.class);
private JLabel lblError = new JLabel();
private JRadioButton exclusivelyButton = new JRadioButton(
"Veranstaltung ausschließlich in den ausgewählten Räumen anzeigen");
private JRadioButton prioritizedButton = new JRadioButton(
"Veranstaltung mit höherer Priorität in den ausgewählten Räumen anzeigen");
// private JCheckBox limitToAllowedUsers = new
// JCheckBox("Nur für die ausgewählten Benutzern anzeigen", false);
/**
* Flag for the initialization state
*/
private boolean initDone = false;
/**
* List of ID's of locations to set the selection to when we finished
* initializing
*/
private List<Integer> preselection;
private JCheckBoxTree locationTree = new JCheckBoxTree();;
private HashMap<Integer, DefaultMutableTreeNode> locationNodesMap = new HashMap<Integer, DefaultMutableTreeNode>();
/**
* Constructor. Initializes the grid layout, the location lists and
* initializes the data
*/
public LocationSelector() {
// build the grid
GridManager grid = new GridManager(this, 3);
grid.add(locationTree, 3).fill(true, true).expand(true, true);
grid.nextRow();
// the radio buttons, default is to show the lecture exclusively in the
// selected locations
exclusivelyButton.setSelected(true);
ButtonGroup group = new ButtonGroup();
group.add(exclusivelyButton);
group.add(prioritizedButton);
JPanel radioPanel = new JPanel();
radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.PAGE_AXIS));
radioPanel.add(exclusivelyButton);
radioPanel.add(prioritizedButton);
grid.add(radioPanel, 3);
grid.nextRow();
grid.add(Box.createVerticalGlue(), 3);
grid.nextRow();
grid.add(lblError, 3);
grid.nextRow();
grid.finish(true);
// initialise the data
init();
}
public JCheckBoxTree getTree() {
return locationTree;
}
/**
* Async fetching of the list of the locations from the server
*/
public void init() {
QuickTimer.scheduleOnce(new Task() {
List<Location> locsList = null;
@Override
public void fire() {
locsList = MetaDataCache.getLocations();
Gui.asyncExec(new Runnable() {
@Override
public void run() {
initDone = fillLocationsList(locsList);
// check if preselection was set before we were done
// initialising
if (initDone && preselection != null) {
setSelectionInternal(preselection);
}
}
});
}
});
}
/**
* Sets the list of available locations. Should be called with the list of
* locations given by the server. This function will build the tree
* represented by the 'locationid'/'parentlocationid' fields of locations
* and set the generated tree as model for the JCheckboxTree used in the UI
* of this widget.
*
* @param list
* of locations to set the available list to
* @return true if setting the list worked, false otherwise
*/
public boolean fillLocationsList(final List<Location> locations) {
if (locations == null)
return false;
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(
Config.getLastSatellite());
DefaultTreeModel treeModel = new DefaultTreeModel(rootNode, true);
// build map containing the node object for each location
for (Location loc : locations) {
if (loc == null)
continue;
locationNodesMap.put(loc.getLocationId(),
new DefaultMutableTreeNode(loc, true));
}
// now go over nodes and insert em
for (Integer id : locationNodesMap.keySet()) {
Location location = MetaDataCache.getLocationById(id);
if (location == null)
continue;
// determine which node this is a child of
DefaultMutableTreeNode parentNode = null;
if (location.getParentLocationId() == 0)
parentNode = rootNode;
else
parentNode = locationNodesMap.get(location
.getParentLocationId());
// insert the current node in the tree model
treeModel.insertNodeInto(
locationNodesMap.get(location.getLocationId()), // what
parentNode, // parent?
parentNode.getChildCount()); // index
}
locationTree.setModel(treeModel);
locationTree.updateUI();
return true;
}
/**
* Setter for the "show only in selection" checkbox
*
* @param true to enable the "limited" radio button, false to enable the
* other
*/
public void setOnlyInSelection(boolean limited) {
exclusivelyButton.setSelected(limited);
prioritizedButton.setSelected(!limited);
}
/**
* Getter for the "show only in selection" checkbox
*
* @return true if checked, false otherwise
*/
public boolean getOnlyInSelection() {
return exclusivelyButton.isSelected();
}
/**
* Sets the selection to the locations corresponding to the given list of
* id's
*
* @param list
* of integers to set the locations to
*/
public void setSelectedLocationsAsIds(List<Integer> list) {
if (list == null) {
LOGGER.error("No list given!");
return;
}
preselection = list;
// if we are initialised, we need to explicitly set the selection
if (initDone)
setSelectionInternal(preselection);
}
/**
* Gets the selected locations as a list of id's
*
* @return list of ids of the selection as Integer, empty list if the
* selection is empty
*/
public List<Integer> getSelectedLocationsAsIds() {
TreePath[] paths = locationTree.getCheckedPaths();
if (paths == null || paths.length == 0) {
return null;
}
// first try to reduce the amount of locations pushed to the server
// by removing those which are implicitly selected:
// all children of a node in the selection => keep only parent
List<TreePath> treePathList = minify(paths);
// now get the locationId of the nodes in the TreePath list
List<Integer> idList = new ArrayList<Integer>(treePathList.size());
// iterate over the paths
for (TreePath path : treePathList) {
if (path == null)
continue;
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) path
.getLastPathComponent();
if (currentNode == null)
continue;
Object currentDataObject = currentNode.getUserObject();
if (currentDataObject instanceof Location) {
Location currentLocation = (Location) currentDataObject;
idList.add(currentLocation.getLocationId());
}
}
Collections.sort(idList);
return idList;
}
private List<TreePath> minify(final TreePath[] paths) {
// transform the array of paths to a list of leaf nodes
List<TreePath> leavesPathsList = new ArrayList<TreePath>();
for (TreePath path : paths) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path
.getLastPathComponent();
if (node != null && node.isLeaf()) {
leavesPathsList.add(path);
}
}
// now get the root node of the tree and start a depth-first search for
// leaves
DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode) locationTree
.getModel().getRoot();
Enumeration<?> depthEnum = rootNode.depthFirstEnumeration();
while (depthEnum.hasMoreElements()) {
DefaultMutableTreeNode current = (DefaultMutableTreeNode) depthEnum
.nextElement();
// descend til we found a leaf
if (!current.isLeaf()) {
continue;
}
// got a leaf, check the children of its parent
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) current
.getParent();
// this shouldnt happen but lets be safe...
if (parent == null || parent.isLeaf())
continue;
// marker to see if all children are selected
boolean allChildrenSelected = true;
Enumeration<?> children = parent.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) children
.nextElement();
// check if this child is in our paths list
if (!leavesPathsList.contains(getPath(child))) {
// current child not in selection, we can stop the search of
// this child's parent
allChildrenSelected = false;
break;
}
}
// if all children were selected, remove them from the list
if (allChildrenSelected) {
LOGGER.debug("EXCLUDING FROM: " + getPath(parent));
Enumeration<?> enumeration = parent.children();
while (enumeration.hasMoreElements()) {
DefaultMutableTreeNode nod = (DefaultMutableTreeNode) enumeration
.nextElement();
leavesPathsList.remove(getPath(nod)); // remove already
// checks for
// existence
}
// add this parent since we had only leaves in the
// leavesPathList
leavesPathsList.add(getPath(parent));
}
}
return leavesPathsList;
}
/**
* Helper to get the TreePath of the given TreeNode
* @param treeNode
* @return
*/
public static TreePath getPath(TreeNode treeNode) {
List<Object> nodes = new ArrayList<Object>();
if (treeNode != null) {
nodes.add(treeNode);
treeNode = treeNode.getParent();
while (treeNode != null) {
nodes.add(0, treeNode);
treeNode = treeNode.getParent();
}
}
return nodes.isEmpty() ? null : new TreePath(nodes.toArray());
}
/**
* Internal helper to set the selection of the list of location
* corresponding the the given list of ids
*
* @param list
* of id's to set the selected locations to
*/
private void setSelectionInternal(List<Integer> list) {
if (list == null || list.isEmpty()) {
LOGGER.error("No list given for preselection!");
return;
}
// get the paths in the tree of the given id list of nodes
List<TreePath> selectedPathsList = new ArrayList<TreePath>(list.size());
for (Integer id : list) {
DefaultMutableTreeNode current = locationNodesMap.get(id);
if (current == null)
continue;
TreePath path = new TreePath(current.getPath());
selectedPathsList.add(path);
}
locationTree.setCheckedState(selectedPathsList);
}
}
@SuppressWarnings("serial")
class LocationRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(JList<? extends Object> list,
Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value instanceof Location) {
return super.getListCellRendererComponent(list,
((Location) value).getLocationName(), index, isSelected,
cellHasFocus);
}
return super.getListCellRendererComponent(list, value, index,
isSelected, cellHasFocus);
}
}