/***
* Copyright (c) 2008, Endless Loop Software, Inc.
*
* This file is part of EgoNet.
*
* EgoNet is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* EgoNet is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.egonet.util.listbuilder;
import javax.swing.*;
import javax.swing.event.*;
import org.egonet.gui.author.CategoryInputPane;
import org.egonet.model.Shared.AlterNameModel;
import org.egonet.model.question.Selection;
import java.awt.BorderLayout;
import java.awt.event.*;
import com.jgoodies.forms.layout.*;
import com.jgoodies.forms.builder.*;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Map;
public class ListBuilder extends JPanel implements Observer {
/**
* Contains a list of Selection items that can be observed as the contents
* of the list change.
*/
public ObservableList<Selection> elementList;
/**
* When true, a form is displayed where entries in the list may be added or
* deleted. When false, only the list of options is displayed.
*/
private boolean editable = true;
/**
* If adjacent is active, the selected item of the list may be toggled to be
* adjacent via a 2-state button. Toggled entries in the list, selected or
* not, will be highlighted with a different color. Adjacent "Selection"
* items will have their Adjacent field set to true IFF they have been
* selected by the 2-state button being toggled ON.
*/
private boolean adjacencyActive = false;
/**
* If preset lists are active, you may choose a pre-set list of options
* (States, Gender, Yes/No, i.e. NON-custom). When false, custom is the only
* option and no list is shown.
*/
private boolean presetListsActive = false;
/**
* When true, users will be able to directly set Selection values in the
* same manner they can enter string values.
*/
private boolean letUserPickValues = false;
/**
* When max size is selected, users will not be able to add more items once
* the number of items has reached max size. The delete button will still be
* available, however, so they may delete items and then they will again be
* allowed to add items up to the max size of the list.
*/
private int maxSize = -1;
private String elementName = "";
private String title = "";
private String description = "";
private JList<Selection> jList = null;;
private JList<String> knownAltersForm;
private HashMap <String, Integer> knownAltersList = null;
private JScrollPane jScrollPane = null;
private JPanel panelTopHalf = null;
private JPanel panelTopRight = null;
private JPanel panelButtons = null;
private JButton buttonAdjacency = null;
private JButton buttonDelete = null;
private JButton buttonAdd = null;
private JTextArea labelDescription = null;
private JLabel listCounter;
private JTextField firstName, lastName, itemName, value;
private CellConstraints constraints = new CellConstraints();
private AlterNameModel alterNameModel = null;
private static final Map<String, List<Selection>> presets = ListBuilderPresets.getPresets();
private static final String CHOOSE_PRESET_INSTRUCTION = "Choose from preset options";
private ArrayList <String> altersToRemove = new ArrayList <String>();
private boolean isTypedAlter = false;
public ListBuilder() {
super();
elementList = new ObservableList<Selection>();
build();
addListObserver(this);
}
public void addListObserver(Observer ob) {
elementList.addObserver(ob);
}
/**
* item has been updated. update our JList.
*/
public void update(Observable o, Object arg) {
jList.setListData(elementList.toArray(new Selection[0]));
jList.revalidate();
jList.repaint();
if(listCounter != null)
listCounter.setText(jList.getModel().getSize() + " items listed.");
}
private void build() {
// purge anything old
removeAll();
// logger.info("Building List builder...");
jList = new JList<Selection>();
jList.setCellRenderer(new SelectionListCellRenderer());
jList.setListData(elementList.toArray(new Selection[0]));
jScrollPane = new JScrollPane(jList);
// is NOT editable, we only use the main panel i.e. "this"
if (!editable) {
FormLayout layout = new FormLayout(
"2dlu, fill:max(pref;200dlu):grow, 2dlu",
"2dlu, fill:pref:grow, 2dlu");
setLayout(layout);
add(jScrollPane, constraints.xy(2, 2));
invalidate();
return;
}
// is editable, so we start using subpanels for layouts
FormLayout mainLayout = new FormLayout(
"2dlu, fill:min(pref;300dlu):grow, 2dlu",
"2dlu, fill:pref:grow, 2dlu, fill:pref:grow, 2dlu");
setLayout(mainLayout);
// combine top and bottom panels
add(buildTop(), constraints.xy(2, 2));
add(buildBottom(), constraints.xy(2,4));
// mainLayout.invalidateLayout(this);
// when the list selection is changed
jList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
clearTextFields();
return;
}
JList list = (JList) e.getSource();
Selection selection = (Selection) list.getSelectedValue();
if (selection == null)
return;
// if this is a name list, attempt to parse out the pieces,
// otherwise leave them empty
String firstStr = "";
String lastStr = "";
if (isNameList()) {
if(selection.getString() == null) // really unexpected/broken
return;
String[] parts = selection.getString().split(", ");
if (parts.length != 2) {
// TODO: warn person
return;
} else {
lastStr = parts[0];
firstStr = parts[1];
}
}
setTextFields(firstStr, lastStr, selection.getString(), ""
+ selection.getValue());
}
});
invalidate();
}
private JComponent buildTop() {
// logger.info("Building Top....");
// top half panel
panelTopHalf = new JPanel();
FormLayout topHalfLayout = new FormLayout(
"2dlu, fill:145dlu:grow, 2dlu, fill:145dlu:grow, 2dlu",
"2dlu, fill:pref:grow, 2dlu");
panelTopHalf.setLayout(topHalfLayout);
listCounter = new JLabel();
JPanel scrollPanel = new JPanel(new BorderLayout());
scrollPanel.add(listCounter, BorderLayout.SOUTH);
scrollPanel.add(jScrollPane, BorderLayout.CENTER);
panelTopHalf.add(scrollPanel, constraints.xy(2, 2));
panelTopHalf.add(buildTopRight(), constraints.xy(4, 2));
return panelTopHalf;
}
private JComponent buildTopRight() {
// logger.info("Building Top Right....:"+ isPresetListsActive());
panelTopRight = new JPanel();
FormLayout panelTopRightLayout = new FormLayout(
"2dlu, fill:75dlu:grow, 2dlu",
"2dlu, fill:20dlu:grow, 2dlu, 35dlu, 2dlu, pref:grow, 2dlu, fill:pref:grow, 2dlu");
panelTopRight.setLayout(panelTopRightLayout);
JLabel labelTitle = new JLabel(title);
panelTopRight.add(labelTitle, constraints.xy(2, 2));
labelDescription = new JTextArea(description);
labelDescription.setEditable(false);
labelDescription.setLineWrap(true);
labelDescription.setWrapStyleWord(true);
//labelDescription.setRows(10); labelDescription.setColumns(30);
JScrollPane sp = new JScrollPane(labelDescription);
panelTopRight.add(sp, constraints.xy(2, 4));
if (isPresetListsActive()) {
// logger.info("Presets are supposed to be active");
final JComboBox<String> comboPresets = new JComboBox<String>();
comboPresets.addItem(CHOOSE_PRESET_INSTRUCTION);
for (String presetName : presets.keySet())
comboPresets.addItem(presetName);
panelTopRight.add(comboPresets, constraints.xy(2, 6));
comboPresets.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
Object item = e.getItem();
List<Selection> options = presets.get(item);
if (options != null) {
setListSelections(options);
comboPresets.setSelectedIndex(0);
clearTextFields();
}
}
}
});
}
panelTopRight.add(buildInputPanel(), constraints.xy(2, 8));
return panelTopRight;
}
private JComponent buildInputPanel() {
final String colspec = "2dlu, fill:pref:grow, 2dlu";
FormLayout inputLayout = new FormLayout(colspec);
DefaultFormBuilder formBuilder = new DefaultFormBuilder(inputLayout);
formBuilder.append("");
formBuilder.nextRow();
formBuilder.setLeadingColumnOffset(1);
firstName = new JTextField(); firstName.setName("firstName");
lastName =new JTextField(); lastName.setName("lastName");
itemName = new JTextField(); itemName.setName("itemName");
value = new JTextField(); value.setName("itemName");
//formBuilder.nextRow();
firstName.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent keyEvent) {
isTypedAlter = true;
}
public void keyPressed(KeyEvent keyEvent) {
}
public void keyReleased(KeyEvent keyEvent) {
saveDataForSelectionOfList(keyEvent);
if ((keyEvent.getKeyCode() == KeyEvent.VK_ENTER))
lastName.grabFocus();
}
});
lastName.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent keyEvent) {
isTypedAlter = true;
buttonAdd.setEnabled(true);
}
public void keyPressed(KeyEvent keyEvent) {
}
public void keyReleased(KeyEvent keyEvent) {
saveDataForSelectionOfList(keyEvent);
if ((keyEvent.getKeyCode() == KeyEvent.VK_ENTER) && isLetUserPickValues())
value.grabFocus();
else if ((keyEvent.getKeyCode() == KeyEvent.VK_ENTER))
firstName.grabFocus();
}
});
itemName.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent keyEvent) {
buttonAdd.setEnabled(true);
isTypedAlter = true;
}
public void keyPressed(KeyEvent keyEvent) {
}
public void keyReleased(KeyEvent keyEvent) {
saveDataForSelectionOfList(keyEvent);
if ((keyEvent.getKeyCode() == KeyEvent.VK_ENTER) && isLetUserPickValues())
value.grabFocus();
}
});
value.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent keyEvent) {
buttonAdd.setEnabled(true);
}
public void keyPressed(KeyEvent keyEvent) {
}
public void keyReleased(KeyEvent keyEvent) {
saveDataForSelectionOfList(keyEvent);
// logger.info("source=" + keyEvent.getSource() + " id=" + keyEvent.getID() +
// " when=" + keyEvent.getWhen() + " modifiers=" + keyEvent.getModifiers() +
// " keyCode=" + keyEvent.getKeyCode() + " keyChar=" + keyEvent.getKeyChar());
// if you typed on value, blank the selection
if ((keyEvent.getKeyCode() == KeyEvent.VK_ENTER)) {
if (isNameList())
firstName.grabFocus();
else
itemName.grabFocus();
}
}
});
// couple different configurations here
if (alterNameModel != null && alterNameModel.equals(AlterNameModel.FIRST_LAST)) {
formBuilder.append("First Name: ", firstName, false);
formBuilder.append("Last Name: ", lastName, true);
} else {
String itm = (alterNameModel != null) ? "" : "Item ";
formBuilder.append(itm + "Name: ", itemName, false);
}
if (letUserPickValues)
formBuilder.append("Value: ", value, true);
//Build known alters list form.
knownAltersForm = new JList<String>();
if(knownAltersList != null)
knownAltersForm.setListData(knownAltersList.keySet().toArray(new String[0]));
//knownAltersForm.setBorder(BorderFactory.createLineBorder(Color.gray) );
knownAltersForm.setVisibleRowCount(-1);
JScrollPane knownAltersScrollBar = new JScrollPane(knownAltersForm);
knownAltersScrollBar.setPreferredSize(new Dimension(200,80));
formBuilder.append("Or select someone already known: ", knownAltersScrollBar, true);
knownAltersForm.addListSelectionListener(new ListSelectionListener(){
public void valueChanged(ListSelectionEvent e){
buttonAdd.setEnabled(true);
}
});
return formBuilder.getPanel();
}
/**
* Called when the text fields have keys typed in them. This method does not
* handle FOCUS at all -- it only stores data. If you'd like the selected
* item to change, or the focus to move, upon saving data, do it outside of
* this method in your handler.
*
* @param keyEvent
*/
private void saveDataForSelectionOfList(KeyEvent keyEvent) {
Object selectionObject = jList.getSelectedValue();
boolean itemSelectedFromList = selectionObject != null && selectionObject instanceof Selection;
boolean enterPressed = (keyEvent.getKeyCode() == KeyEvent.VK_ENTER);
boolean shouldBlank = (keyEvent.getSource() == value)
|| (keyEvent.getSource() == lastName && isNameList() && !isLetUserPickValues())
|| (keyEvent.getSource() == itemName && !isNameList() && !isLetUserPickValues());
if (itemSelectedFromList) {
Selection selection = (Selection) selectionObject;
// OLD item
try {
convertTextFieldsToSelection(selection);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
"Could not parse your integer value", "Value problem",
JOptionPane.ERROR_MESSAGE);
value.setText(selection.getValue() + "");
return;
}
if (enterPressed && shouldBlank) {
// someone HAS pressed enter
jList.clearSelection();
clearTextFields();
}
jList.updateUI();
} else if (!itemSelectedFromList) {
// NEW item -- don't do anything until they hit enter on the LAST field
if (enterPressed && shouldBlank) {
Selection selection = new Selection();
try {
convertTextFieldsToSelection(selection);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
"Could not parse your integer value",
"Value problem", JOptionPane.ERROR_MESSAGE);
return;
}
//we can't add unknown alters. we can add known alters.
if(maxSize != -1 && knownAltersList.size() + 1 > maxSize && !knownAltersList.containsKey(selection.getString()))
{
JOptionPane.showMessageDialog(this,
"You cannot add new alters! Select someone already known or proceed to the next question.",
"Maximum alter limit reached", JOptionPane.ERROR_MESSAGE);
} else if(selection.getString() == null ||
selection.getString().trim().isEmpty())
{
JOptionPane.showMessageDialog(this,
"Must enter a name to add an alter!",
"Empty alter won't be added", JOptionPane.ERROR_MESSAGE);
} else if(contains(elementList, selection)) {
JOptionPane.showMessageDialog(this,
"Name is already in the list!",
"Identical alter won't be added", JOptionPane.ERROR_MESSAGE);
} else {
elementList.add(selection);
addAlterAppearance(selection);
// someone HAS pressed enter
jList.clearSelection();
knownAltersForm.clearSelection();
}
clearTextFields();
buttonAdd.setEnabled(false);
isTypedAlter = false;
}
}
}
private void convertTextFieldsToSelection(Selection selection)
throws NumberFormatException {
if (isLetUserPickValues() && !value.getText().equals("")
&& !value.getText().equals("-")) {
int intVal = Integer.parseInt(value.getText());
selection.setValue(intVal);
}
//If there is some known alter selected sets selection to this alter.
if (!knownAltersForm.isSelectionEmpty())
{
selection.setString(knownAltersForm.getSelectedValue().toString());
}
else if (isNameList()) {
// selection.setString(lastName.getText() + ", " +
// firstName.getText());
selection.setString(firstName.getText() + " " + lastName.getText());
} else {
selection.setString(itemName.getText());
}
}
private void clearTextFields() {
setTextFields("", "", "", "");
}
private void setTextFields(String first, String last, String item,
String values) {
firstName.setText(first);
lastName.setText(last);
itemName.setText(item);
value.setText(values);
}
private JComponent buildBottom() {
// button panel
ButtonBarBuilder builder = new ButtonBarBuilder();
buttonAdd = new JButton("Add to list");
buttonAdd.setEnabled(false);
buttonAdd.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
// simulate enter key being pressed at last name
if(isNameList()) {
try {
KeyEvent keyEvent = new KeyEvent(lastName, 0, 0, 0, KeyEvent.VK_ENTER, '\n');
saveDataForSelectionOfList(keyEvent);
} catch (Exception ex) {
// eat failure, keypress simulation failed
}
}
else { //if (itemName.getText() != null && value.getText() != null) {
try {
KeyEvent keyEvent = new KeyEvent(value, 0, 0, 0,
KeyEvent.VK_ENTER, '\n');
saveDataForSelectionOfList(keyEvent);
} catch (Exception ex) {
// eat failure, keypress simulation failed
}
}
}
});
buttonDelete = new JButton("Delete selected item");
buttonDelete.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
Object[] selections = jList.getSelectedValues();
for (Object o : selections)
{
/*if(knownAltersList.get(o.toString()) != null)
{*/
//if the alter to remove has only 1 appearance
//we must remove it from the global alter list.
int appearances = (int) knownAltersList.get(o.toString());
appearances--;
if( appearances == 0)
{
altersToRemove.add(o.toString());
knownAltersList.remove(o.toString());
}
//else, update the appearance value.
else
{
knownAltersList.put(o.toString(),appearances);
}
/* }else
{
altersToRemove.add(o.toString());
}*/
elementList.remove(o);
}
jList.clearSelection();
}
});
builder.addGridded(buttonAdd);
builder.addGridded(buttonDelete);
if (isAdjacencyActive()) {
buttonAdjacency = new JButton("Mark selected item adjacent");
buttonAdjacency.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
Object[] selections = jList.getSelectedValues();
for (Object o : selections) {
if (o instanceof Selection) {
((Selection) o).setAdjacent(!((Selection) o)
.isAdjacent());
}
}
jList.revalidate();
jList.repaint();
}
});
builder.addGridded(buttonAdjacency);
}
panelButtons = builder.getPanel();
return panelButtons;
}
public static void main(String[] args) throws Exception {
ListBuilder listBuilder = new ListBuilder();
listBuilder.setName("name field");
listBuilder.setTitle("title field");
String s = "";
for (int i = 0; i < 20; i++)
s += "Lorem ipsum dolor. ";
listBuilder.setDescription(s);
listBuilder.setEditable(true);
listBuilder.setLetUserPickValues(true);
//listBuilder.setAdjacencyActive(true);
CategoryInputPane frame = new CategoryInputPane(null, new JList());
frame.pack();
frame.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
public boolean isEditable() {
return editable;
}
public void setEditable(boolean editable) {
this.editable = editable;
build();
}
public ObservableList<Selection> getElementList() {
return elementList;
}
public void setElementList(ObservableList<Selection> elementList) {
this.elementList = elementList;
build();
}
public String getElementName() {
return elementName;
}
public void setElementName(String elementName) {
this.elementName = elementName;
}
public void setMaxListSize(int maxSize) {
this.maxSize = maxSize;
build();
}
public int getMaxListSize() {
return maxSize;
}
public List<Selection> getSelections() {
List<Selection> selectionList = new ArrayList<Selection>();
for(int i = 0 ; i < elementList.size(); i++)
{
Object o = elementList.get(i);
if (o.getClass().equals(Selection.class))
selectionList.add((Selection)o);
/* Since alter question prompt can contain an alter
* from a previous question, we can't break the loop,
* because we don't want to count this repeated alters.
Break the loop may cause some new alters don't be counted*/
// escape loop if maxSize is set and we're at it
/*if(maxSize != -1 && selectionList.size() == maxSize)
break;*/
}
return selectionList;
}
public void setSelections(List<Selection> selections) {
elementList.removeAll();
elementList.addAll(selections);
build();
}
public List<Selection> getListSelections() {
return getSelections();
}
public void setListSelections(List<Selection> selections) {
setSelections(selections);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isNameList() {
return alterNameModel != null && alterNameModel.equals(AlterNameModel.FIRST_LAST);
}
public boolean isAdjacencyActive() {
return adjacencyActive;
}
public void setAdjacencyActive(boolean adjacencyActive) {
this.adjacencyActive = adjacencyActive;
build();
}
public boolean isPresetListsActive() {
return presetListsActive;
}
public void setPresetListsActive(boolean presetListsActive) {
this.presetListsActive = presetListsActive;
build();
}
public String [] getListStrings() {
List<Selection> listSelections = getListSelections();
String[] listStrings = new String[listSelections.size()];
int i = 0;
for (Selection selection : listSelections)
listStrings[i++] = selection.getString();
return listStrings;
}
public void setListStrings(String[] listStrings) {
elementList.removeAll();
for (int i = 0; i < listStrings.length; i++) {
elementList.add(new Selection(listStrings[i], i, i, false));
}
build();
}
public boolean isLetUserPickValues() {
return letUserPickValues;
}
public void setLetUserPickValues(boolean letUserPickValues) {
this.letUserPickValues = letUserPickValues;
build();
}
public void requestFocusOnFirstVisibleComponent() {
if (this.isNameList())
itemName.requestFocusInWindow();
else
firstName.requestFocusInWindow();
}
public void setNameModel(AlterNameModel alterNameModel) {
this.alterNameModel = alterNameModel;
build();
}
public AlterNameModel getAlterNameModel() {
return alterNameModel;
}
public <ITEM> boolean contains(ObservableList<Selection> list, Selection o) {
for(int i = 0; i < list.size(); i++) {
Selection obj = list.get(i);
if(obj.getString().equals(o.getString()))
return true;
}
return false;
}
//Sets the known alter hashmap. <Alter, number of appearances>
public void setKnownAlters(HashMap <String, Integer> list){
knownAltersList = list;
}
//Return a list with the alters to be removed.
public ArrayList <String> getAltersToRemove()
{
return altersToRemove;
}
//Is there some named typed in the input fields?
public boolean isTypedAlter()
{
return isTypedAlter;
}
//Adds a appearance for the alter "o" into knownAlters hashmap. We add the appearance manually
//because generating the full hashmap everytime the user inputs an alter, could have
//high computacional cost, and could decrease the performance.
private void addAlterAppearance(Selection o)
{
int value;
if(knownAltersList.containsKey(o.getString()))
{
value = (int) knownAltersList.get(o.getString());
value++;
knownAltersList.put(o.getString(), value);
}
else
{
value = 1;
knownAltersList.put(o.getString(), value);
}
}
}