/*** * 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); } } }