ListEditorDialog.java
001 /*
002  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009  *
010  *  Valentin Tablan 16/10/2001
011  *
012  *  $Id: ListEditorDialog.java 13302 2010-12-22 21:52:46Z thomas_heitz $
013  *
014  */
015 
016 package gate.gui;
017 
018 import java.awt.Component;
019 import java.awt.event.ActionEvent;
020 import java.awt.event.ActionListener;
021 import java.awt.event.KeyEvent;
022 import java.util.*;
023 
024 import javax.swing.*;
025 
026 import gate.Gate;
027 import gate.creole.ResourceData;
028 import gate.util.*;
029 
030 /**
031  * A simple editor for Collection values.
032  */
033 public class ListEditorDialog extends JDialog {
034 
035   /**
036    * Contructs a new ListEditorDialog.
037    @param owner the component this dialog will be centred on.
038    @param data a Collection with the initial values. This will not be changed,
039    * its values will be cached and if the user selects the OK option a new list
040    * with the updated contents will be returned.
041    @param itemType the type of the elements in the collection in the form of a
042    * fully qualified class name
043    */
044   public ListEditorDialog(Component owner, Collection data, String itemType) {
045     this(owner, data, null, itemType);
046   }
047   
048   /**
049    * Contructs a new ListEditorDialog.
050    @param owner the component this dialog will be centred on.
051    @param data a Collection with the initial values. This will not be changed,
052    * its values will be cached and if the user selects the OK option a new list
053    * with the updated contents will be returned.
054    @param collectionType the class of the <code>data</code> collection.
055    * If null the class will be inferred from <code>data</code>.  If
056    <code>data</code> is also null, {@link List} will be assumed.
057    @param itemType the type of the elements in the collection in the form of a
058    * fully qualified class name
059    */
060   public ListEditorDialog(Component owner, Collection data,
061           Class<? extends Collection> collectionType, String itemType) {
062     super(MainFrame.getInstance());
063     if(collectionType == null) {
064       if(data != null) {
065         collectionType = data.getClass();
066       }
067       else {
068         collectionType = List.class;
069       }
070     }
071     this.itemType = itemType == null "java.lang.String" : itemType;
072     setLocationRelativeTo(owner);
073     initLocalData(data, collectionType);
074     initGuiComponents();
075     initListeners();
076   }
077 
078   protected void initLocalData(Collection data,
079           Class<? extends Collection> collectionType){
080     try{
081       ResourceData rData = (ResourceData)Gate.getCreoleRegister().get(itemType);
082       itemTypeClass = rData == null ?
083                       Class.forName(itemType, true, Gate.getClassLoader()) :
084                       rData.getResourceClass();
085     }catch(ClassNotFoundException cnfe){
086       throw new GateRuntimeException(cnfe.toString());
087     }
088 
089     finiteType = Gate.isGateType(itemType);
090 
091     ResourceData rData = (ResourceData)Gate.getCreoleRegister().get(itemType);
092 
093     String typeDescription = null;
094     if(List.class.isAssignableFrom(collectionType)) {
095       typeDescription = "List";
096       allowDuplicates = true;
097     }
098     else {
099       if(Set.class.isAssignableFrom(collectionType)) {
100         typeDescription = "Set";
101         allowDuplicates = false;
102       }
103       else {
104         typeDescription = "Collection";
105         allowDuplicates = true;
106       }
107       
108       if(SortedSet.class.isAssignableFrom(collectionType)
109               && data != null) {
110         comparator = ((SortedSet)data).comparator();
111       }
112       if(comparator == null) {
113         comparator = new NaturalComparator();
114       }
115     }
116     
117     listModel = new DefaultListModel();
118     if(data != null){
119       if(comparator == null) {
120         for(Object elt : data) {
121           listModel.addElement(elt);
122         }
123       }
124       else {
125         Object[] dataArray = data.toArray();
126         Arrays.sort(dataArray, comparator);
127         for(Object elt : dataArray) {
128           listModel.addElement(elt);
129         }
130       }
131     }
132 
133     setTitle(typeDescription + " of "
134             ((rData== null? itemType :rData.getName()));
135     addAction = new AddAction();
136     removeAction = new RemoveAction();
137   }
138 
139   protected void initGuiComponents(){
140     getContentPane().setLayout(new BoxLayout(getContentPane(),
141                                              BoxLayout.Y_AXIS));
142 
143     //the editor component
144     JComponent editComp = null;
145     if(finiteType){
146       editComp = combo = new JComboBox(new ResourceComboModel());
147       combo.setRenderer(new ResourceRenderer());
148       if(combo.getModel().getSize() 0){
149         combo.getModel().setSelectedItem(combo.getModel().getElementAt(0));
150       }
151     }else{
152       editComp = textField = new JTextField(20);
153     }
154 
155     getContentPane().add(editComp);
156     getContentPane().add(Box.createVerticalStrut(5));
157 
158     //the buttons box
159     Box buttonsBox = Box.createHorizontalBox();
160     addBtn = new JButton(addAction);
161     addBtn.setMnemonic(KeyEvent.VK_A);
162     removeBtn = new JButton(removeAction);
163     removeBtn.setMnemonic(KeyEvent.VK_R);
164     buttonsBox.add(Box.createHorizontalGlue());
165     buttonsBox.add(addBtn);
166     buttonsBox.add(Box.createHorizontalStrut(5));
167     buttonsBox.add(removeBtn);
168     buttonsBox.add(Box.createHorizontalGlue());
169     getContentPane().add(buttonsBox);
170     getContentPane().add(Box.createVerticalStrut(5));
171 
172     //the list component
173     Box horBox = Box.createHorizontalBox();
174     listComponent = new JList(listModel);
175     listComponent.setSelectionMode(ListSelectionModel.
176                                    MULTIPLE_INTERVAL_SELECTION);
177     listComponent.setCellRenderer(new ResourceRenderer());
178     horBox.add(new JScrollPane(listComponent));
179     //up down buttons if the user should control the ordering
180     if(comparator == null) {
181       Box verBox = Box.createVerticalBox();
182       verBox.add(Box.createVerticalGlue());
183       moveUpBtn = new JButton(MainFrame.getIcon("up"));
184       verBox.add(moveUpBtn);
185       verBox.add(Box.createVerticalStrut(5));
186       moveDownBtn = new JButton(MainFrame.getIcon("down"));
187       verBox.add(moveDownBtn);
188       verBox.add(Box.createVerticalGlue());
189       horBox.add(Box.createHorizontalStrut(3));
190       horBox.add(verBox);
191     }
192     horBox.add(Box.createHorizontalStrut(3));
193     getContentPane().add(horBox);
194     getContentPane().add(Box.createVerticalStrut(5));
195 
196     //the bottom buttons
197     buttonsBox = Box.createHorizontalBox();
198     buttonsBox.add(Box.createHorizontalGlue());
199     okButton = new JButton("OK");
200     buttonsBox.add(okButton);
201     buttonsBox.add(Box.createHorizontalStrut(5));
202     cancelButton = new JButton("Cancel");
203     buttonsBox.add(cancelButton);
204     buttonsBox.add(Box.createHorizontalGlue());
205     getContentPane().add(buttonsBox);
206   }
207 
208   protected void initListeners(){
209     okButton.addActionListener(new ActionListener() {
210       public void actionPerformed(ActionEvent e) {
211         userCancelled = false;
212         setVisible(false);
213       }
214     });
215 
216     cancelButton.addActionListener(new ActionListener() {
217       public void actionPerformed(ActionEvent e) {
218         userCancelled = true;
219         setVisible(false);
220       }
221     });
222 
223     // define keystrokes action bindings at the level of the main window
224     InputMap inputMap = ((JComponent)this.getContentPane()).
225       getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
226     ActionMap actionMap = ((JComponent)this.getContentPane()).getActionMap();
227     inputMap.put(KeyStroke.getKeyStroke("ENTER")"Apply");
228     actionMap.put("Apply"new AbstractAction() {
229       public void actionPerformed(ActionEvent e) {
230         okButton.doClick();
231       }
232     });
233     inputMap.put(KeyStroke.getKeyStroke("ESCAPE")"Cancel");
234     actionMap.put("Cancel"new AbstractAction() {
235       public void actionPerformed(ActionEvent e) {
236         cancelButton.doClick();
237       }
238     });
239 
240     if(moveUpBtn != null) {
241       moveUpBtn.addActionListener(new ActionListener() {
242         public void actionPerformed(ActionEvent e) {
243           int rows[] = listComponent.getSelectedIndices();
244           if(rows == null || rows.length == 0){
245             JOptionPane.showMessageDialog(
246                 ListEditorDialog.this,
247                 "Please select some items to be moved ",
248                 "GATE", JOptionPane.ERROR_MESSAGE);
249           }else{
250             //we need to make sure the rows are sorted
251             Arrays.sort(rows);
252             //get the list of items
253             for(int i = 0; i < rows.length; i++){
254               int row = rows[i];
255               if(row > 0){
256                 //move it up
257                 Object value = listModel.remove(row);
258                 listModel.add(row - 1, value);
259               }
260             }
261             //restore selection
262             for(int i = 0; i < rows.length; i++){
263               int newRow = -1;
264               if(rows[i0newRow = rows[i1;
265               else newRow = rows[i];
266               listComponent.addSelectionInterval(newRow, newRow);
267             }
268           }
269   
270         }//public void actionPerformed(ActionEvent e)
271       });
272     }
273 
274 
275     if(moveDownBtn != null) {
276       moveDownBtn.addActionListener(new ActionListener() {
277         public void actionPerformed(ActionEvent e) {
278           int rows[] = listComponent.getSelectedIndices();
279           if(rows == null || rows.length == 0){
280             JOptionPane.showMessageDialog(
281                 ListEditorDialog.this,
282                 "Please select some items to be moved ",
283                 "GATE", JOptionPane.ERROR_MESSAGE);
284           else {
285             //we need to make sure the rows are sorted
286             Arrays.sort(rows);
287             //get the list of items
288             for(int i = rows.length - 1; i >= 0; i--){
289               int row = rows[i];
290               if(row < listModel.size() -1){
291                 //move it down
292                 Object value = listModel.remove(row);
293                 listModel.add(row + 1, value);
294               }
295             }
296             //restore selection
297             for(int i = 0; i < rows.length; i++){
298               int newRow = -1;
299               if(rows[i< listModel.size() 1newRow = rows[i1;
300               else newRow = rows[i];
301               listComponent.addSelectionInterval(newRow, newRow);
302             }
303           }
304   
305         }//public void actionPerformed(ActionEvent e)
306       });
307     }
308 
309   
310   
311   }
312 
313   /**
314    * Make this dialog visible allowing the editing of the collection.
315    * If the user selects the <b>OK</b> option a new list with the updated
316    * contents will be returned; it the <b>Cancel</b> option is selected this
317    * method return <tt>null</tt>.  Note that this method always returns
318    * a <code>List</code>.  When used for a resource parameter this is
319    * OK, as GATE automatically converts this to the right collection
320    * type when the resource is created, but if you use this class
321    * anywhere else to edit a non-<code>List</code> collection you will
322    * have to copy the result back into a collection of the appropriate
323    * type yourself.
324    */
325   public List showDialog(){
326     pack();
327     userCancelled = true;
328     setModal(true);
329     super.setVisible(true);
330     return userCancelled ? null : Arrays.asList(listModel.toArray());
331   }
332 
333   /**
334    * test code
335    */
336   public static void main(String[] args){
337     try{
338       Gate.init();
339     }catch(Exception e){
340       e.printStackTrace();
341     }
342     JFrame frame = new JFrame("Foo frame");
343 
344     ListEditorDialog dialog = new ListEditorDialog(frame,
345                                                    new ArrayList(),
346                                                    "java.lang.Integer");
347 
348     frame.setSize(300300);
349     frame.setVisible(true);
350     System.out.println(dialog.showDialog());
351   }
352 
353   /**
354    * Adds an element to the list from the editing component located at the top
355    * of this dialog.
356    */
357   protected class AddAction extends AbstractAction{
358     AddAction(){
359       super("Add");
360       putValue(SHORT_DESCRIPTION, "Add the edited value to the list");
361     }
362     public void actionPerformed(ActionEvent e){
363       if(finiteType){
364         listModel.addElement(combo.getSelectedItem());
365       }else{
366         Object value = null;
367         //convert the value to the proper type
368         String stringValue = textField.getText();
369         if(stringValue == null || stringValue.length() == 0stringValue = null;
370 
371         if(itemTypeClass.isAssignableFrom(String.class)){
372           //no conversion necessary
373           value = stringValue;
374         }else{
375           //try conversion
376           try{
377             value = itemTypeClass.getConstructor(new Class[]{String.class}).
378                                   newInstancenew Object[]{stringValue} );
379           }catch(Exception ex){
380             JOptionPane.showMessageDialog(
381                 ListEditorDialog.this,
382                 "Invalid value!\nIs it the right type?",
383                 "GATE", JOptionPane.ERROR_MESSAGE);
384             return;
385           }
386         }
387         
388         if(comparator == null) {
389           // for a straight list, add at the end always
390           listModel.addElement(value);
391         }
392         else {
393           // otherwise, find where to insert
394           int index = 0;
395           while(index < listModel.size()
396                   && comparator.compare(value, listModel.get(index)) 0) {
397             index++;
398           }
399           if(index == listModel.size()) {
400             // moved past the end of the list, and the new value is
401             // not contained in the list, so add at the end
402             listModel.addElement(value);
403           }
404           else {
405             if(allowDuplicates
406                     || comparator.compare(value, listModel.get(index)) 0) {
407               // insert at the found index if either duplicates are allowed
408               // or it's not a duplicate
409               listModel.add(index, value);
410             }
411           }
412         }
413         textField.setText("");
414         textField.requestFocus();
415       }
416     }
417   }
418 
419   /**
420    * Removes the selected element(s) from the list
421    */
422   protected class RemoveAction extends AbstractAction{
423     RemoveAction(){
424       super("Remove");
425       putValue(SHORT_DESCRIPTION, "Remove the selected value(s) from the list");
426     }
427 
428     public void actionPerformed(ActionEvent e){
429       int[] indices = listComponent.getSelectedIndices();
430       Arrays.sort(indices);
431       for(int i = indices.length -1; i >= 0; i--){
432         listModel.remove(indices[i]);
433       }
434     }
435   }
436 
437 
438   /**
439    * A model for a combobox containing the loaded corpora in the system
440    */
441   protected class ResourceComboModel extends AbstractListModel
442                                   implements ComboBoxModel{
443 
444     public int getSize(){
445       //get all corpora regardless of their actual type
446       java.util.List loadedResources = null;
447       try{
448         loadedResources = Gate.getCreoleRegister().
449                                getAllInstances(itemType);
450       }catch(GateException ge){
451         ge.printStackTrace(Err.getPrintWriter());
452       }
453 
454       return loadedResources == null : loadedResources.size();
455     }
456 
457     public Object getElementAt(int index){
458       //get all corpora regardless of their actual type
459       java.util.List loadedResources = null;
460       try{
461         loadedResources = Gate.getCreoleRegister().
462                                getAllInstances(itemType);
463       }catch(GateException ge){
464         ge.printStackTrace(Err.getPrintWriter());
465       }
466       return loadedResources == nullnull : loadedResources.get(index);
467     }
468 
469     public void setSelectedItem(Object anItem){
470       if(anItem == nullselectedItem = null;
471       else selectedItem = anItem;
472     }
473 
474     public Object getSelectedItem(){
475       return selectedItem;
476     }
477 
478     void fireDataChanged(){
479       fireContentsChanged(this, 0, getSize());
480     }
481 
482     Object selectedItem = null;
483   }
484 
485   /**
486    * The type of the elements in the list
487    */
488   String itemType;
489 
490   /**
491    * The Class for the elements in the list
492    */
493   Class itemTypeClass;
494 
495   /**
496    * The GUI compoenent used to display the list
497    */
498   JList listComponent;
499 
500   /**
501    * Comobox used to select among values for GATE types
502    */
503   JComboBox combo;
504 
505   /**
506    * Text field used to input new arbitrary values
507    */
508   JTextField textField;
509 
510   /**
511    * Used to remove the selected element in the list;
512    */
513   JButton removeBtn;
514 
515   /**
516    * Used to add a new value to the list
517    */
518   JButton addBtn;
519 
520   /**
521    * Moves up one or more items in the list
522    */
523   JButton moveUpBtn;
524 
525   /**
526    * Moves down one or more items in the list
527    */
528   JButton moveDownBtn;
529 
530   /**
531    * The model used by the {@link #listComponent}
532    */
533   DefaultListModel listModel;
534 
535   /**
536    * Does the item type have a finite range (i.e. should we use the combo)?
537    */
538   boolean finiteType;
539 
540   /**
541    * An action that adds the item being edited to the list
542    */
543   Action addAction;
544 
545   /**
546    * An action that removes the item(s) currently selected from the list
547    */
548   Action removeAction;
549 
550   /**
551    * The OK button for this dialog
552    */
553   JButton okButton;
554 
555   /**
556    * The cancel button for this dialog
557    */
558   JButton cancelButton;
559 
560   /**
561    * Did the user press the cancel button?
562    */
563   boolean userCancelled;
564   
565   /**
566    * Does this collection permit duplicate entries?
567    */
568   boolean allowDuplicates;
569   
570   /**
571    * Comparator to use to sort the entries displayed in the list.
572    * If this dialog was created to edit a List, this will be null
573    * and the ordering provided by the user will be preserved.  If
574    * the dialog was created from a Set or plain Collection this
575    * will be either the set's own comparator (if a SortedSet) or a
576    {@link NaturalComparator}.
577    */
578   Comparator comparator;
579   
580   /**
581    * A comparator that uses the objects' natural order if the item
582    * class of the collection implements Comparable, and compares
583    * their <code>toString</code> representations if not.
584    <code>null</code> is always treated as less than anything
585    * non-<code>null</code>.
586    */
587   protected class NaturalComparator implements Comparator {
588     public int compare(Object a, Object b) {
589       if(a == null) {
590         if(b == null) {
591           return 0;
592         }
593         else {
594           return -1;
595         }
596       }
597       else if(b == null) {
598         return 1;
599       }
600       else if(Comparable.class.isAssignableFrom(itemTypeClass)) {
601         return ((Comparable)a).compareTo(b);
602       }
603       else {
604         return a.toString().compareTo(b.toString());
605       }
606     }
607   }
608 }