001: package org.skunk.swing;
002:
003: import java.awt.Dimension;
004: import java.awt.GridBagConstraints;
005: import java.awt.GridBagLayout;
006: import java.awt.GridLayout;
007: import java.awt.Insets;
008: import java.awt.event.ActionEvent;
009: import java.awt.event.ActionListener;
010: import java.util.ArrayList;
011: import java.util.ListIterator;
012: import javax.swing.Box;
013: import javax.swing.DefaultListModel;
014: import javax.swing.DefaultListSelectionModel;
015: import javax.swing.JButton;
016: import javax.swing.JLabel;
017: import javax.swing.JList;
018: import javax.swing.JPanel;
019: import javax.swing.JScrollPane;
020: import javax.swing.ListSelectionModel;
021: import javax.swing.event.ListDataListener;
022: import javax.swing.event.ListSelectionEvent;
023: import javax.swing.event.ListSelectionListener;
024: import org.skunk.trace.Debug;
025:
026: /**
027: * <P>a widget for making an ordered selection from a list of items.
028: * Two JLists are separated vertically by a stack of four buttons,
029: * add, remove, up and down. The add and remove buttons move
030: * items from the left Jlist to the right and vice versa, respectively.
031: * The up and down buttons move selected items of the right JList up
032: * and down the list.
033: *
034: * <P>Internationalized applications should set the label properties.
035: */
036: public class DualListPanel extends JPanel {
037: /**
038: * keeps track of all elements in both panes
039: */
040: private DefaultListModel listModel;
041:
042: /**
043: * internal list models for the two JLists
044: */
045: private DefaultListModel sourceModel, sinkModel;
046:
047: /**
048: * selection model keeps track of which goes in which pane
049: */
050: private DefaultListSelectionModel selectionModel;
051:
052: private JList sourceList, sinkList;
053: private JButton addButton, removeButton, upButton, downButton;
054:
055: /**
056: * reference to all items, so when they
057: * bounce back and forth between the two lists they don't get reordered
058: */
059: private ArrayList allItems;
060: private Object[] sourceItems, sinkItems;
061:
062: private JLabel sourceListLabel, sinkListLabel;
063: private String sourceListLabelText, sinkListLabelText;
064:
065: //default values for button labels -- should not be used in real life, not i18n!!!!!
066: private static final String ADD = "-->";
067: private static final String REMOVE = "<--";
068: private static final String UP = "up";
069: private static final String DOWN = "down";
070: private static final String POSSIBLE_VALUES = "Possible Values";
071: private static final String SELECTED_VALUES = "Selected Values";
072:
073: /**
074: * constructs a DualListPanel with the given source and sink values.
075: * @param sourceItems the source values (will appear in the left JList)
076: * @param sinkItems the sink values (will appear in the right JList)
077: */
078: public DualListPanel(Object[] sourceItems, Object[] sinkItems) {
079: this (sourceItems, sinkItems, POSSIBLE_VALUES, SELECTED_VALUES);
080: }
081:
082: /**
083: * constructs a DualListPanel with the given source and sink values and labels.
084: * @param sourceItems the source values (will appear in the left JList)
085: * @param sinkItems the sink values (will appear in the right JList)
086: * @param sourceLabel the label text for the left JList
087: * @param sinkLabel the label text for the right JList
088: */
089: public DualListPanel(Object[] sourceItems, Object[] sinkItems,
090: String sourceLabel, String sinkLabel) {
091: super ();
092: this .allItems = new ArrayList();
093: sourceModel = new DefaultListModel();
094: sinkModel = new DefaultListModel();
095: this .sourceListLabelText = sourceLabel;
096: this .sinkListLabelText = sinkLabel;
097: this .sourceItems = sourceItems;
098: this .sinkItems = sinkItems;
099: //add initial data to models
100: initModels();
101: initComponents();
102: initLayout();
103:
104: }
105:
106: private void _addListItem(Object item, boolean chosen) {
107: synchronized (allItems) {
108: if (!allItems.contains(item))
109: allItems.add(item);
110: int itemIndex = allItems.size() - 1;
111: }
112: DefaultListModel tmp = (chosen) ? sinkModel : sourceModel;
113: if (!tmp.contains(item))
114: tmp.addElement(item);
115: tmp = (!chosen) ? sinkModel : sourceModel;
116: if (tmp.contains(item))
117: tmp.removeElement(item);
118: }
119:
120: /* addListItem() and removeListItem() need to be written */
121:
122: // public void addListItem(Object item, boolean chosen)
123: // {
124: // }
125: // public void removeListItem(Object item)
126: // {
127: // }
128: /**
129: * returns panel and models to original state at time of construction.
130: */
131: public void reset() {
132: Debug.trace(this , Debug.DP3, "in reset()");
133: sourceModel.clear();
134: sinkModel.clear();
135: initModels();
136: repaint();
137: }
138:
139: /**
140: * populates the JLists with the given source and sink values.
141: * @param sourceItems the new contents of the left JList
142: * @param sinkITems the new contents of the right JList
143: */
144: public void reset(Object[] sourceItems, Object[] sinkItems) {
145: this .sourceItems = sourceItems;
146: this .sinkItems = sinkItems;
147: reset();
148: }
149:
150: /**
151: * sets the contents of the right JList to the given values,
152: * and places other values currently in the DualListPanel in the left JList
153: * @param chosenItems the items the right JList should contain
154: */
155: public void reset(Object[] chosenItems) {
156: Debug.trace(this , Debug.DP3, "in reset(Object[])");
157: ArrayList tmp = new ArrayList();
158: for (ListIterator lit = allItems.listIterator(); lit.hasNext();) {
159: Object nextObj = lit.next();
160: if (!inArray(chosenItems, nextObj)) {
161: tmp.add(nextObj);
162: }
163: }
164: reset(tmp.toArray(), chosenItems);
165: }
166:
167: private static boolean inArray(Object[] objArray, Object thing) {
168: for (int i = 0; i < objArray.length; i++) {
169: if (objArray[i].equals(thing))
170: return true;
171: }
172: return false;
173: }
174:
175: private void initModels() {
176: if (sourceItems != null) {
177: for (int i = 0; i < sourceItems.length; i++) {
178: _addListItem(sourceItems[i], false);
179: }
180: }
181: if (sinkItems != null) {
182: for (int i = 0; i < sinkItems.length; i++) {
183: _addListItem(sinkItems[i], true);
184: }
185: }
186: }
187:
188: /**
189: * returns the items in the right JList
190: * @return the chosen items
191: */
192: public Object[] getChosenItems() {
193: return sinkModel.toArray();
194: }
195:
196: /**
197: * add a listener to the data model of the left JList
198: * @param ldl the listener
199: */
200: public void addListDataListener(ListDataListener ldl) {
201: sinkModel.addListDataListener(ldl);
202: }
203:
204: /**
205: * remove a listener from the data model of the left JList
206: * @param ldl the listener
207: */
208: public void removeListDataListener(ListDataListener ldl) {
209: sinkModel.removeListDataListener(ldl);
210: }
211:
212: private void initComponents() {
213: //create the JLists
214:
215: sourceList = new JList(sourceModel);
216: sourceList
217: .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
218: sourceList
219: .addListSelectionListener(new SourceSelectionListener());
220:
221: sinkList = new JList(sinkModel);
222: sinkList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
223: sinkList.addListSelectionListener(new SinkSelectionListener());
224:
225: //labels
226: sourceListLabel = new JLabel(sourceListLabelText);
227: sinkListLabel = new JLabel(sinkListLabelText);
228:
229: //create buttons
230: addButton = new JButton(ADD);
231: addButton.setEnabled(false);
232: removeButton = new JButton(REMOVE);
233: removeButton.setEnabled(false);
234: upButton = new JButton(UP);
235: upButton.setEnabled(false);
236: downButton = new JButton(DOWN);
237: downButton.setEnabled(false);
238:
239: //add listeners
240: addButton.addActionListener(new AddAction());
241: removeButton.addActionListener(new RemoveAction());
242: upButton.addActionListener(new UpAction());
243: downButton.addActionListener(new DownAction());
244: }
245:
246: private void initLayout() {
247: GridBagConstraints gbc = new GridBagConstraints();
248: gbc.gridx = 0;
249: gbc.gridy = 0;
250: gbc.gridwidth = 1;
251: gbc.gridheight = 1;
252: gbc.fill = gbc.NONE;
253: gbc.insets = new Insets(2, 2, 2, 2);
254: gbc.anchor = gbc.CENTER;
255:
256: setLayout(new GridBagLayout());
257: gbc.weighty = 0.1;
258: add(sourceListLabel, gbc);
259: gbc.weighty = 0.0;
260: gbc.gridx = 2;
261: add(sinkListLabel, gbc);
262:
263: gbc.gridy++;
264: gbc.gridx = gbc.RELATIVE;
265: gbc.weightx = 1.0;
266: gbc.weighty = 0.9;
267: add(new JScrollPane(sourceList), gbc);
268:
269: JPanel buttonBox = new JPanel(new GridLayout(4, 1));
270: buttonBox.add(addButton);
271: buttonBox.add(removeButton);
272: buttonBox.add(upButton);
273: buttonBox.add(downButton);
274:
275: gbc.weightx = 0.5;
276: gbc.weighty = 0.0;
277: add(buttonBox, gbc);
278:
279: gbc.weightx = 1.0;
280: add(new JScrollPane(sinkList), gbc);
281: }
282:
283: /**
284: * provides access to the add button
285: * @return the add button
286: */
287: public JButton getAddButton() {
288: return addButton;
289: }
290:
291: /**
292: * provides access to the remove button
293: * @return the remove button
294: */
295: public JButton getRemoveButton() {
296: return removeButton;
297: }
298:
299: /**
300: * provides access to the up button
301: * @return the up button
302: */
303: public JButton getUpButton() {
304: return upButton;
305: }
306:
307: /**
308: * provides access to the down button
309: * @return the down button
310: */
311: public JButton getDownButton() {
312: return downButton;
313: }
314:
315: /**
316: * sets the preferred size of both lists.
317: * @param d the preferred size
318: */
319: public void setListPreferredSize(Dimension d) {
320: sourceList.setPreferredSize(d);
321: sinkList.setPreferredSize(d);
322: revalidate();
323: }
324:
325: /**
326: * sets the label of the add button.
327: * @param addLabel the new label
328: */
329: public void setAddLabel(String addLabel) {
330: addButton.setText(addLabel);
331: }
332:
333: /**
334: * sets the label of the remove button
335: * @param removeLabel the new label
336: */
337: public void setRemoveLabel(String removeLabel) {
338: removeButton.setText(removeLabel);
339: }
340:
341: /**
342: * sets the label of the up button
343: * @param upLabel the new label
344: */
345: public void setUpLabel(String upLabel) {
346: upButton.setText(upLabel);
347: }
348:
349: /**
350: * sets the label of the down button
351: * @param downLabel the new label
352: */
353: public void setDownLabel(String downLabel) {
354: downButton.setText(downLabel);
355: }
356:
357: /**
358: * sets the label of the left JList
359: * @param sourceListLabelText the new label
360: */
361: public void setSourceListLabelText(String sourceListLabelText) {
362: this .sourceListLabel.setText(sourceListLabelText);
363: }
364:
365: /**
366: * sets the label of the right JList
367: * @param sinkListLabelText the new label
368: */
369: public void setSinkListLabelText(String sinkListLabelText) {
370: this .sinkListLabel.setText(sinkListLabelText);
371: }
372:
373: private int getOriginalSourceItemIndex(Object sourceItem) {
374: return allItems.indexOf(sourceItem);
375: }
376:
377: private int getInsertIndex(Object sourceItem) {
378: int originalIndex = getOriginalSourceItemIndex(sourceItem);
379: int i = 0;
380: while (i < sourceModel.getSize()) {
381: if (getOriginalSourceItemIndex(sourceModel.getElementAt(i)) > originalIndex) {
382: break;
383: }
384: i++;
385: }
386: return i;
387: }
388:
389: private void checkAddButton() {
390: addButton
391: .setEnabled(sourceList.getSelectedIndices().length != 0);
392: }
393:
394: private void checkRemoveButton() {
395: removeButton
396: .setEnabled(sinkList.getSelectedIndices().length != 0);
397: }
398:
399: private void checkUpAndDownButtons() {
400: int[] selected = sinkList.getSelectedIndices();
401: boolean enableUp = false;
402: boolean enableDown = false;
403: int max = sinkModel.getSize() - 1;
404: for (int i = 0; i < selected.length; i++) {
405: int index = selected[i];
406: if (index > 0)
407: enableUp = true;
408: if (index < max)
409: enableDown = true;
410: if (enableUp && enableDown)
411: break;
412: }
413: upButton.setEnabled(enableUp);
414: downButton.setEnabled(enableDown);
415: }
416:
417: class SourceSelectionListener implements ListSelectionListener {
418: public void valueChanged(ListSelectionEvent lousy) {
419: checkAddButton();
420: }
421: }
422:
423: class SinkSelectionListener implements ListSelectionListener {
424: public void valueChanged(ListSelectionEvent lousy) {
425: checkRemoveButton();
426: checkUpAndDownButtons();
427: }
428: }
429:
430: class UpAction implements ActionListener {
431: public void actionPerformed(ActionEvent ae) {
432: Debug.trace(this , Debug.DP3, "in UpAction");
433: int[] selected = sinkList.getSelectedIndices();
434: for (int i = 0; i < selected.length; i++) {
435: int oldIndex = selected[i];
436: if (oldIndex > 0) {
437: int newIndex = oldIndex - 1;
438: sinkModel.insertElementAt(sinkModel
439: .remove(oldIndex), newIndex);
440: sinkList.setSelectedIndex(newIndex);
441: }
442: }
443: checkUpAndDownButtons();
444: checkRemoveButton();
445: }
446: }
447:
448: class DownAction implements ActionListener {
449: public void actionPerformed(ActionEvent ae) {
450: Debug.trace(this , Debug.DP3, "in DownAction");
451: int[] selected = sinkList.getSelectedIndices();
452: for (int i = 0; i < selected.length; i++) {
453: int oldIndex = selected[i];
454: if (oldIndex < sinkModel.getSize() - 1) {
455: int newIndex = oldIndex + 1;
456: sinkModel.insertElementAt(sinkModel
457: .remove(oldIndex), newIndex);
458: sinkList.setSelectedIndex(newIndex);
459: }
460: }
461: checkUpAndDownButtons();
462: }
463: }
464:
465: class AddAction implements ActionListener {
466: public void actionPerformed(ActionEvent ae) {
467: Debug.trace(this , Debug.DP3, "in AddAction");
468: //take selected item in source and move it into sink
469: int[] selected = sourceList.getSelectedIndices();
470: for (int i = 0; i < selected.length; i++) {
471: Object o = sourceModel.remove(selected[i]);
472: sinkModel.addElement(o);
473: }
474: checkAddButton();
475: checkUpAndDownButtons();
476: }
477: }
478:
479: class RemoveAction implements ActionListener {
480: public void actionPerformed(ActionEvent ae) {
481: Debug.trace(this , Debug.DP3, "in RemoveAction");
482: //take selected item in sink and move it into source
483: int[] selected = sinkList.getSelectedIndices();
484: for (int i = 0; i < selected.length; i++) {
485: Object o = sinkModel.remove(selected[i]);
486:
487: sourceModel.insertElementAt(o, getInsertIndex(o));
488: }
489: checkRemoveButton();
490: checkUpAndDownButtons();
491: }
492: }
493: }
494:
495: /* $Log: DualListPanel.java,v $
496: /* Revision 1.5 2001/01/04 06:02:49 smulloni
497: /* added more javadoc documentation.
498: /*
499: /* Revision 1.4 2000/11/09 23:35:13 smullyan
500: /* log added to every Java file, with the help of python. Lock stealing
501: /* implemented, and treatment of locks made more robust.
502: /* */
|